All files / src/hooks/useResetPassword useResetPassword.ts

100% Statements 13/13
80% Branches 8/10
100% Functions 2/2
100% Lines 13/13

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                                                      1x 20x   10x   10x   10x           6x     6x         6x   1x         1x         1x         1x         2x                            
/**
 * useResetPassword Hook - Amplify v6 Direct Integration
 * Story 1.2.2a: Reset Password Confirmation
 *
 * GREEN Phase: Full implementation with Cognito error mapping
 */
 
import { useMutation } from '@tanstack/react-query';
import { confirmResetPassword } from 'aws-amplify/auth';
import { ensureAmplifyConfigured } from '@/config/amplify';
 
interface ResetPasswordInput {
  email: string;
  code: string;
  newPassword: string;
}
 
interface UseResetPasswordOptions {
  onSuccess?: () => void;
  onError?: (error: ResetPasswordError) => void;
}
 
export interface ResetPasswordError extends Error {
  type: 'invalidCode' | 'expiredCode' | 'invalidPassword' | 'rateLimitExceeded' | 'unknownError';
  statusCode?: number;
}
 
export const useResetPassword = (options?: UseResetPasswordOptions) => {
  return useMutation<void, ResetPasswordError, ResetPasswordInput>({
    mutationFn: async ({ email, code, newPassword }: ResetPasswordInput) => {
      try {
        // Amplify is configured lazily (perf/public-homepage-followup #2) — ensure before use.
        await ensureAmplifyConfigured();
        // Amplify v6: confirmResetPassword updates password with code
        await confirmResetPassword({
          username: email,
          confirmationCode: code,
          newPassword: newPassword,
        });
      } catch (error: unknown) {
        console.error('[useResetPassword] Password reset failed:', error);
 
        const errorCode =
          (error as { code?: string; name?: string }).code ||
          (error as { code?: string; name?: string }).name ||
          'UNKNOWN_ERROR';
 
        // Map Cognito errors to app errors
        switch (errorCode) {
          case 'CodeMismatchException':
            throw Object.assign(new Error('Invalid verification code'), {
              type: 'invalidCode' as const,
              statusCode: 400,
            });
          case 'ExpiredCodeException':
            throw Object.assign(new Error('Verification code has expired'), {
              type: 'expiredCode' as const,
              statusCode: 400,
            });
          case 'InvalidPasswordException':
            throw Object.assign(new Error('Password does not meet requirements'), {
              type: 'invalidPassword' as const,
              statusCode: 400,
            });
          case 'LimitExceededException':
            throw Object.assign(new Error('Too many password reset attempts'), {
              type: 'rateLimitExceeded' as const,
              statusCode: 429,
            });
          default:
            throw Object.assign(
              new Error((error as { message?: string }).message || 'Password reset failed'),
              {
                type: 'unknownError' as const,
                statusCode: 500,
              }
            );
        }
      }
    },
    onSuccess: options?.onSuccess,
    onError: options?.onError,
  });
};