All files / src/hooks/useForgotPassword useForgotPassword.ts

100% Statements 14/14
77.77% Branches 7/9
100% Functions 2/2
100% Lines 14/14

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                                                1x 14x   7x   7x   7x   2x         5x     5x         5x   2x           1x 1x   1x         1x                            
/**
 * useForgotPassword Hook - Amplify v6 Direct Integration
 * Story 1.2.2: Implement Forgot Password Flow (UPDATED October 2025)
 *
 * ARCHITECTURE CHANGE: Migrated from backend API to Amplify v6 direct integration
 * OLD: Frontend → Backend API → Cognito
 * NEW: Frontend → Amplify v6 → Cognito (direct)
 */
 
import { useMutation } from '@tanstack/react-query';
import { resetPassword } from 'aws-amplify/auth';
import { ensureAmplifyConfigured } from '@/config/amplify';
 
interface UseForgotPasswordOptions {
  onSuccess?: () => void;
  onError?: (error: ForgotPasswordError) => void;
}
 
// Extended Error type to include rate limit info
export interface ForgotPasswordError extends Error {
  type: 'rateLimitExceeded' | 'userNotFound' | 'invalidParameter' | 'unknownError' | 'networkError';
  statusCode?: number;
}
 
export const useForgotPassword = (options?: UseForgotPasswordOptions) => {
  return useMutation<void, ForgotPasswordError, string>({
    mutationFn: async (email: string) => {
      try {
        // Amplify is configured lazily (perf/public-homepage-followup #2) — ensure before use.
        await ensureAmplifyConfigured();
        // Amplify v6: resetPassword sends code to user's email automatically
        const result = await resetPassword({ username: email });
 
        console.log('[useForgotPassword] Password reset code sent:', result);
 
        // Cognito automatically sends email with 6-digit code
        // User will enter code in Story 1.2.2a confirmation screen
      } catch (error: unknown) {
        console.error('[useForgotPassword] 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 'LimitExceededException':
            throw Object.assign(new Error('Too many password reset attempts'), {
              type: 'rateLimitExceeded' as const,
              statusCode: 429,
            });
          case 'UserNotFoundException':
            // Email enumeration prevention: Don't throw, return success
            console.log('[useForgotPassword] User not found (suppressed for security)');
            return; // Success (enumeration prevention)
          case 'InvalidParameterException':
            throw Object.assign(new Error('Invalid email address'), {
              type: 'invalidParameter' as const,
              statusCode: 400,
            });
          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,
  });
};