Files
tabungin/EMAIL_OTP_FIX.md
dwindown 249f3a9d7d feat: remove OTP gate from transactions, fix categories auth, add implementation plan
- Remove OtpGateGuard from transactions controller (OTP verified at login)
- Fix categories controller to use authenticated user instead of TEMP_USER_ID
- Add comprehensive implementation plan document
- Update .env.example with WEB_APP_URL
- Prepare for admin dashboard development
2025-10-11 14:00:11 +07:00

4.8 KiB

Email OTP Sending During Login - FIXED

🐛 Problem:

Email OTP was not being sent during login flow. It only worked when manually requested from the profile page.

Symptoms:

  • User logs in with email/password (has email OTP enabled)
  • Redirected to OTP page
  • No email received
  • Console shows no OTP code
  • User stuck on OTP page

Root Cause:

The login flow was checking if OTP was required and returning a temp token, but never actually sending the email OTP!

// OLD CODE - No email sent!
if (requiresOtp) {
  return {
    requiresOtp: true,
    availableMethods: {
      email: user.otpEmailEnabled,
      totp: user.otpTotpEnabled,
    },
    tempToken: this.generateTempToken(user.id, user.email),
  };
}

Fixes Applied:

1. Injected OtpService into AuthService

// auth.module.ts
imports: [
  PrismaModule,
  PassportModule,
  forwardRef(() => OtpModule),  // Added OtpModule
  JwtModule.register({...}),
],

// auth.service.ts
constructor(
  private readonly prisma: PrismaService,
  private readonly jwtService: JwtService,
  @Inject(forwardRef(() => OtpService))  // Injected OtpService
  private readonly otpService: OtpService,
) {}

2. Send Email OTP During Login

// In login() method
if (requiresOtp) {
  // Send email OTP if enabled
  if (user.otpEmailEnabled) {
    try {
      await this.otpService.sendEmailOtp(user.id);  // ← SEND EMAIL!
    } catch (error) {
      console.error('Failed to send email OTP during login:', error);
      // Continue anyway - user can request resend
    }
  }

  return {
    requiresOtp: true,
    availableMethods: {
      email: user.otpEmailEnabled,
      totp: user.otpTotpEnabled,
    },
    tempToken: this.generateTempToken(user.id, user.email),
  };
}

3. Send Email OTP During Google Login

// In googleLogin() method
if (requiresOtp) {
  // Send email OTP if enabled
  if (user.otpEmailEnabled) {
    try {
      await this.otpService.sendEmailOtp(user.id);  // ← SEND EMAIL!
    } catch (error) {
      console.error('Failed to send email OTP during Google login:', error);
    }
  }

  return {
    requiresOtp: true,
    availableMethods: {...},
    tempToken: this.generateTempToken(user.id, user.email),
  };
}

4. Added Separate Verification Method

Created verifyEmailOtpForLogin() that verifies the code without enabling the feature:

// otp.service.ts
async verifyEmailOtpForLogin(userId: string, code: string): Promise<boolean> {
  const stored = this.emailOtpStore.get(userId);
  
  if (!stored || new Date() > stored.expiresAt || stored.code !== code) {
    return false;
  }
  
  this.emailOtpStore.delete(userId);
  return true;
}

5. Updated Login Verification

// In verifyOtpAndLogin() method
if (method === 'email') {
  const isValid = await this.otpService.verifyEmailOtpForLogin(userId, otpCode);
  if (!isValid) {
    throw new UnauthorizedException('Invalid or expired email OTP code');
  }
}

📝 Files Modified:

  1. apps/api/src/auth/auth.module.ts

    • Added forwardRef(() => OtpModule) to imports
  2. apps/api/src/auth/auth.service.ts

    • Injected OtpService
    • Send email OTP in login() method
    • Send email OTP in googleLogin() method
    • Use verifyEmailOtpForLogin() for verification
  3. apps/api/src/otp/otp.service.ts

    • Added verifyEmailOtpForLogin() method
    • Keeps existing verifyEmailOtp() for setup

🧪 Testing:

Test Email/Password Login with Email OTP:

  1. Login with email/password
  2. Email OTP should be sent automatically
  3. Check console for: 📧 OTP Code for user@example.com: 123456
  4. Enter code on OTP page
  5. Should login successfully

Test Google Login with Email OTP:

  1. Click "Continue with Google"
  2. Authenticate
  3. Email OTP should be sent automatically
  4. Redirected to OTP page
  5. Check console for OTP code
  6. Enter code
  7. Should login successfully

What Now Works:

Email OTP sent during login - Automatically when user has it enabled
Email OTP sent during Google OAuth - Works for both flows
Proper verification - Uses dedicated login verification method
Console logging - Shows OTP code in development
Webhook integration - Sends to n8n if configured


🎯 Expected Behavior:

  1. User logs in (email/password or Google)
  2. If email OTP enabled:
    • Email is sent automatically
    • Console shows: 📧 OTP Code for user@example.com: 123456
    • User redirected to OTP page
  3. User enters code
  4. Code verified
  5. User logged in successfully

Email OTP should now work during login! Test it now! 🚀