Files
tabungin/FINAL_FIXES.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

5.3 KiB

Final Fixes - Change Password & Resend OTP

🐛 Issues Fixed:

1. Change Password Not Functioning

2. Resend OTP Email Error


🔧 Fix 1: Change Password Implementation

Backend Changes:

Added Endpoint (auth.controller.ts):

@Post('change-password')
@UseGuards(JwtAuthGuard)
async changePassword(
  @Req() req: RequestWithUser,
  @Body() body: { currentPassword: string; newPassword: string },
) {
  return this.authService.changePassword(
    req.user.userId,
    body.currentPassword,
    body.newPassword,
  );
}

Added Service Method (auth.service.ts):

async changePassword(
  userId: string,
  currentPassword: string,
  newPassword: string,
) {
  // Get user with password hash
  const user = await this.prisma.user.findUnique({
    where: { id: userId },
    select: { passwordHash: true },
  });

  if (!user || !user.passwordHash) {
    throw new BadRequestException('Cannot change password for this account');
  }

  // Verify current password
  const isValid = await bcrypt.compare(currentPassword, user.passwordHash);
  if (!isValid) {
    throw new UnauthorizedException('Current password is incorrect');
  }

  // Hash new password
  const newPasswordHash = await bcrypt.hash(newPassword, 10);

  // Update password
  await this.prisma.user.update({
    where: { id: userId },
    data: { passwordHash: newPasswordHash },
  });

  return {
    success: true,
    message: 'Password changed successfully',
  };
}

Frontend Changes:

Added States (Profile.tsx):

const [currentPassword, setCurrentPassword] = useState("")
const [newPassword, setNewPassword] = useState("")
const [confirmPassword, setConfirmPassword] = useState("")
const [passwordLoading, setPasswordLoading] = useState(false)
const [passwordError, setPasswordError] = useState("")
const [passwordSuccess, setPasswordSuccess] = useState("")

Added Handler:

const handleChangePassword = async () => {
  // Validation
  if (!currentPassword || !newPassword || !confirmPassword) {
    setPasswordError("All fields are required")
    return
  }

  if (newPassword !== confirmPassword) {
    setPasswordError("New passwords do not match")
    return
  }

  if (newPassword.length < 6) {
    setPasswordError("New password must be at least 6 characters")
    return
  }

  // Call API
  await axios.post(`${API}/auth/change-password`, {
    currentPassword,
    newPassword
  })
  
  setPasswordSuccess("Password changed successfully!")
  // Clear fields
}

Updated UI:

  • Connected inputs to state
  • Added onClick handler to button
  • Added loading state
  • Added error/success alerts
  • Added validation

🔧 Fix 2: Resend OTP Email

Problem:

The resend endpoint required a full JWT token, but during OTP verification we only have a temp token.

Solution:

Created a special resend endpoint that accepts temp tokens.

Backend Changes:

Added Endpoint (otp.controller.ts):

@Post('email/resend')
async resendEmailOtp(@Body() body: { tempToken: string }) {
  try {
    // Verify temp token
    const payload = this.jwtService.verify(body.tempToken);
    
    if (!payload.temp) {
      throw new UnauthorizedException('Invalid token type');
    }

    const userId = payload.userId || payload.sub;
    
    // Send OTP
    return this.otpService.sendEmailOtp(userId);
  } catch (error) {
    throw new UnauthorizedException('Invalid or expired token');
  }
}

Frontend Changes:

Updated Resend Handler (OtpVerification.tsx):

// OLD - Used wrong endpoint
await axios.post(`${API_URL}/api/otp/email/send`, {}, {
  headers: { Authorization: `Bearer ${tempToken}` }
})

// NEW - Use resend endpoint with temp token
await axios.post(`${API_URL}/api/otp/email/resend`, {
  tempToken
})

📝 Files Modified:

Backend:

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

    • Added change-password endpoint
  2. apps/api/src/auth/auth.service.ts

    • Added changePassword() method
  3. apps/api/src/otp/otp.controller.ts

    • Added email/resend endpoint
    • Injected JwtService

Frontend:

  1. apps/web/src/components/pages/Profile.tsx

    • Added password change states
    • Added handleChangePassword() handler
    • Updated UI with inputs, validation, alerts
  2. apps/web/src/components/pages/OtpVerification.tsx

    • Updated handleResendEmail() to use new endpoint

🧪 Testing:

Test Change Password:

  1. Go to Profile page
  2. Enter current password
  3. Enter new password
  4. Confirm new password
  5. Click "Update Password"
  6. See success message
  7. Logout and login with new password

Test Resend OTP:

  1. Login with email OTP enabled
  2. On OTP page, wait 30 seconds
  3. Click "Resend Code"
  4. Check console for new OTP code
  5. Enter new code
  6. Login successfully

What Works Now:

Change Password: Full implementation with validation
Resend OTP: Works with temp token
Error Handling: Proper error messages
Success Feedback: Clear success indicators
Loading States: Shows loading during operations
Validation: Client-side validation before API call


Both features are now fully functional! Test them out! 🚀