Skip to content

improvement(mfa): implement MFA unlock email handling with keystore lockout mechanism#5239

Merged
victorvhs017 merged 2 commits intomainfrom
improvement/add-redis-lock-mfa-account-lock-email
Jan 23, 2026
Merged

improvement(mfa): implement MFA unlock email handling with keystore lockout mechanism#5239
victorvhs017 merged 2 commits intomainfrom
improvement/add-redis-lock-mfa-account-lock-email

Conversation

@victorvhs017
Copy link
Contributor

@victorvhs017 victorvhs017 commented Jan 22, 2026

Context

If the POST /api/v2/auth/mfa/verify endpoint is abused, it blocks the account, but it generates a load of emails to be sent to the user.

image

This PR uses the keystore lock to prevent more than one email every 5 minutes.

Steps to verify the change

Send a burst of 100 requests to /api/v2/auth/mfa/verify with an invalid mfaToken. You should get only one email.

Type

  • Fix
  • Feature
  • Improvement
  • Breaking
  • Docs
  • Chore

Checklist

  • Title follows the conventional commit format: type(scope): short description (scope is optional, e.g., fix: prevent crash on sync or fix(api): handle null response).
  • Tested locally
  • Updated docs (if needed)
  • Read the contributing guide

@maidul98
Copy link
Collaborator

maidul98 commented Jan 22, 2026

Snyk checks have passed. No issues have been found so far.

Status Scanner Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 22, 2026

Greptile Summary

This PR implements rate-limiting for MFA unlock emails to prevent email spam when the POST /api/v2/auth/mfa/verify endpoint is abused. The implementation uses Redis-based distributed locking and TTL-based rate-limiting to ensure only one unlock email is sent per user per 5 minutes.

Changes:

  • Added two new keystore prefixes: UserMfaLockoutLock for mutual exclusion and UserMfaUnlockEmailSent for tracking recent sends
  • Injected keystore dependency into the auth login service
  • Wrapped email sending logic in a lock-acquire-check-send pattern with proper cleanup

Critical Issue Found:
The error handling in the email sending block (line 933-934) catches all exceptions indiscriminately, not just lock acquisition failures. This masks legitimate errors from sendMail(), createTokenForUser(), and keystore operations, potentially breaking error monitoring and making debugging difficult.

Confidence Score: 3/5

  • The PR has a critical error handling flaw that masks legitimate errors and could hide operational issues.
  • The core functionality is sound - using Redis locks and TTL-based rate-limiting is the correct approach for this problem. However, the broad try-catch block around the entire email-sending logic (lines 907-939) swallows all exceptions without distinguishing between lock acquisition failures (which are expected and should be silently ignored) and actual errors from sendMail, createTokenForUser, or keystore operations (which should be logged and propagated). This pattern makes it impossible to detect if emails are failing to send, tokens are failing to create, or other critical operations are failing. The fix is straightforward but required for production safety.
  • backend/src/services/auth/auth-login-service.ts - The catch block at line 933-934 needs to distinguish between expected lock failures and actual errors.

Important Files Changed

Filename Overview
backend/src/keystore/keystore.ts Added two new keystore prefixes for MFA lockout handling: UserMfaLockoutLock for distributed locking and UserMfaUnlockEmailSent for rate-limiting unlock emails to one per 5 minutes. No issues detected in the changes.
backend/src/server/routes/index.ts Added keyStore as a dependency to authLoginServiceFactory. This is the correct injection point for the new MFA lockout functionality. No issues detected.
backend/src/services/auth/auth-login-service.ts Added MFA unlock email rate-limiting using keystore locks and expiry. Critical issue: Email is sent INSIDE a try-catch that silently swallows all errors (including non-lock-related errors). This breaks error tracking and could mask legitimate issues. The lock acquisition with retryCount: 0 is correct, but the broad catch block is problematic.

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@victorvhs017 victorvhs017 requested a review from varonix0 January 22, 2026 23:35
@victorvhs017 victorvhs017 requested a review from varonix0 January 23, 2026 15:32
@victorvhs017 victorvhs017 merged commit c53cb74 into main Jan 23, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants