Skip to content

fix(auth): rate-limit OTP verification to block brute force (#70) - #73

Merged
MistryVishwa merged 1 commit into
MistryVishwa:mainfrom
Rudra-clrscr:fix/70-otp-verify-rate-limit
Jul 6, 2026
Merged

fix(auth): rate-limit OTP verification to block brute force (#70)#73
MistryVishwa merged 1 commit into
MistryVishwa:mainfrom
Rudra-clrscr:fix/70-otp-verify-rate-limit

Conversation

@Rudra-clrscr

Copy link
Copy Markdown
Contributor

Closes #70

Problem

The password-reset OTP verification endpoint (app/api/auth/verify-otp/route.ts)
had no attempt limit, lockout, or delay. Although codes are 6 digits with a
10-minute expiry, an attacker could repeatedly guess against the ~900,000
possible codes within the validity window and take over an account.

Fix

Track failed guesses per issued code and invalidate the code once the budget
is spent (the approach proposed in the issue):

  • failed_attempts column on password_reset_otps
    • supabase/migration-otp-failed-attempts.sql — idempotent ADD COLUMN IF NOT EXISTS for existing deployments
    • supabase/migration-otp-table.sql — column added to the CREATE TABLE so fresh installs get it too
  • verify-otp logic
    • increments the counter on every wrong guess and surfaces the remaining attempts in the error message
    • after 5 incorrect guesses, deletes the code and responds 429, forcing a fresh reset request
    • correct codes within the limit verify normally (unchanged happy path)

Because forgot-password deletes and re-inserts a fresh OTP (counter back to 0),
requesting a new code restores the attempt budget.

Acceptance criteria

  • Table tracks failed attempts per OTP
  • N (5) incorrect guesses trigger OTP invalidation
  • Valid codes within the limit continue functioning normally

Deployment note

The migration must be applied to the Supabase database before/with deploy,
otherwise the failed_attempts read/update will error.

Known limitation

The counter is a read-modify-write and is not atomic, so tightly concurrent
requests could slip a few extra guesses past the threshold. The count is still
bounded to a tiny number — nowhere near brute-force range. Happy to follow up
with an atomic Postgres RPC increment if preferred.

Testing

  • tsc --noEmit: clean on the changed route (no new type errors).
  • Not run live: no local .env/Supabase, and it requires the migration applied
    to the DB. Suggested manual check once deployed — request a code, submit 5
    wrong guesses, confirm the 5th returns 429 and the code no longer works.

…shwa#70)

The password-reset OTP verification endpoint had no attempt limit, so an
attacker could guess against the ~900k possible 6-digit codes within the
10-minute validity window and take over accounts.

Track failed guesses per issued code and invalidate it once the budget is
spent:
  - add failed_attempts column to password_reset_otps (new migration + base
    table migration for fresh installs)
  - increment the counter on each wrong guess and surface remaining attempts
  - after 5 incorrect guesses, delete the code and respond 429 so a fresh
    reset request is required

Correct codes within the limit continue to verify normally.
@vercel

vercel Bot commented Jul 5, 2026

Copy link
Copy Markdown

@Rudra-clrscr is attempting to deploy a commit to the vishwamistrylearning-1037's projects Team on Vercel.

A member of the Team first needs to authorize it.

@MistryVishwa MistryVishwa added bug Something isn't working good first issue Good for newcomers ssoc26 Main tag identifying the repository for Social Summer of Code 2026 Medium 30 Points labels Jul 6, 2026
@MistryVishwa
MistryVishwa merged commit bb38bd7 into MistryVishwa:main Jul 6, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working good first issue Good for newcomers Medium 30 Points ssoc26 Main tag identifying the repository for Social Summer of Code 2026

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] No rate limiting on password-reset OTP verification (brute-forceable)

2 participants