All files / src/services/auth emailVerificationService.ts

100% Statements 26/26
86.36% Branches 19/22
100% Functions 2/2
100% Lines 26/26

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169                                                          9x   9x 9x         1x   1x       8x     8x         8x   2x               1x                 2x               1x               1x               1x                               7x   7x 7x       1x   1x       6x     6x         6x     2x               1x               1x               2x                    
/**
 * Email Verification Service Implementation
 * Story 1.2.4: Email Verification Flow
 *
 * Uses AWS Amplify v6 for direct Cognito integration (NO backend proxy needed)
 * Follows the same pattern as authService.ts (Story 1.2.3)
 */
 
import { confirmSignUp, resendSignUpCode } from 'aws-amplify/auth';
import { ensureAmplifyConfigured } from '@/config/amplify';
 
export interface VerifyEmailInput {
  email: string;
  code: string;
}
 
export interface VerifyEmailResult {
  success: boolean;
  error?: {
    code: string;
    message: string;
  };
}
 
/**
 * Verify user email with 6-digit code
 * Wraps Amplify's confirmSignUp function with error handling
 */
export async function verifyEmail(input: VerifyEmailInput): Promise<VerifyEmailResult> {
  try {
    // Amplify is configured lazily (perf/public-homepage-followup #2) — ensure before use.
    await ensureAmplifyConfigured();
    const result = await confirmSignUp({
      username: input.email,
      confirmationCode: input.code,
    });
 
    console.log('[emailVerificationService] Email verified:', result);
 
    return {
      success: true,
    };
  } catch (error: unknown) {
    console.error('[emailVerificationService] Verification failed:', error);
 
    const errorCode =
      (error as { code?: string; name?: string }).code ||
      (error as { code?: string; name?: string }).name ||
      'UNKNOWN_ERROR';
 
    // Map Cognito error codes to user-friendly error codes
    switch (errorCode) {
      case 'CodeMismatchException':
        return {
          success: false,
          error: {
            code: 'INVALID_CODE',
            message: 'Invalid verification code',
          },
        };
      case 'ExpiredCodeException':
        return {
          success: false,
          error: {
            code: 'EXPIRED_CODE',
            message: 'Verification code has expired',
          },
        };
      case 'TooManyFailedAttemptsException':
      case 'LimitExceededException':
        return {
          success: false,
          error: {
            code: 'TOO_MANY_ATTEMPTS',
            message: 'Too many failed attempts',
          },
        };
      case 'NotAuthorizedException':
        return {
          success: false,
          error: {
            code: 'ALREADY_VERIFIED',
            message: 'Email is already verified',
          },
        };
      case 'UserNotFoundException':
        return {
          success: false,
          error: {
            code: 'USER_NOT_FOUND',
            message: 'User not found',
          },
        };
      default:
        return {
          success: false,
          error: {
            code: errorCode,
            message: (error as { message?: string }).message || 'Verification failed',
          },
        };
    }
  }
}
 
/**
 * Resend verification code to user email
 * Wraps Amplify's resendSignUpCode function with error handling
 */
export async function resendVerificationCode(email: string): Promise<VerifyEmailResult> {
  try {
    // Amplify is configured lazily (perf/public-homepage-followup #2) — ensure before use.
    await ensureAmplifyConfigured();
    await resendSignUpCode({
      username: email,
    });
 
    console.log('[emailVerificationService] Verification code resent to:', email);
 
    return {
      success: true,
    };
  } catch (error: unknown) {
    console.error('[emailVerificationService] Resend failed:', error);
 
    const errorCode =
      (error as { code?: string; name?: string }).code ||
      (error as { code?: string; name?: string }).name ||
      'UNKNOWN_ERROR';
 
    // Map Cognito error codes
    switch (errorCode) {
      case 'LimitExceededException':
      case 'TooManyRequestsException':
        return {
          success: false,
          error: {
            code: 'RATE_LIMITED',
            message: 'Too many resend attempts, please wait',
          },
        };
      case 'UserNotFoundException':
        return {
          success: false,
          error: {
            code: 'USER_NOT_FOUND',
            message: 'User not found',
          },
        };
      case 'InvalidParameterException':
        return {
          success: false,
          error: {
            code: 'INVALID_PARAMETER',
            message: 'Invalid email address',
          },
        };
      default:
        return {
          success: false,
          error: {
            code: errorCode,
            message: (error as { message?: string }).message || 'Failed to resend code',
          },
        };
    }
  }
}