Repository navigation
fix(auth): rate limit verification email resends per address (#1000) - #1048
sandrawillow001-afk wants to merge 1 commit into
Conversation
…#1000) The resend button only enforced a 60-second cooldown, so a user could keep requesting verification emails from any auth screen. It now allows three emails per address per hour, shows "Resend in N minutes" while the window is open, and restores that wait after a reload or a second tab. The limit is keyed on a hash of the normalised address (no PII in storage) and recorded only after a request succeeds, so a network failure does not spend the user's allowance. `utils/rate-limit` gained a non-consuming peek and an opt-in localStorage backend; existing callers keep their session-scoped defaults. 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
|
@sandrawillow001-afk Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
CI stops before it reaches this branchBoth workflows fail in their install step, not in any check of this code:
Locally on this branch (installed with |
Closes #1000
Problem
ResendVerificationButtononly enforced a 60-second cooldown. The same button is rendered by every auth surface —LoginPage,VerifyEmailPage(twice) andVerifyEmailBanner— so a user could keep askingPOST /auth/resend-verificationfor new emails indefinitely.Change
src/features/auth/verificationResend.ts(new): the allowance — 3 emails per address per hour — keyed on a hashed, normalised address (no PII in storage), plusformatResendWait()for copy likeResend in 12 minutes.src/features/auth/components/ResendVerificationButton.tsx: checks the allowance before sending, records it only when the request succeeds, and disables itself with the remaining wait once spent. The wait is seeded from storage on mount, so a reload or a second tab does not reset it.src/utils/rate-limit.ts: added a non-consumingpeekRateLimit()and an opt-instorage: 'local'backend. Existing callers (ContactForm,NewsletterForm) keep the previous session-scoped defaults unchanged.Acceptance criteria
/auth/resend-verificationis still required — this repo has no backend.localStorage, so the server limit remains the real boundaryTests
src/features/auth/verificationResend.test.ts(7): three allowed then blocked, wait formatting, per-address isolation, key normalisation and absence of the raw address, peek does not consume, window expiry, storage persistence.src/test/VerificationResend.test.tsx(9): sends and confirms, disabled with "Resend in N minutes" when spent, wait survives remount, blocked clicks do not call the API, failures do not spend the allowance, separate allowances per address, re-enables after the hour, empty email disabled.Verification
verificationResend,VerificationResend, plus the existingForm.test.tsxrate-limit tests as a regression check) — exit 0.npx vitest run: 400 passed, 1 failed test (src/lib/url.test.ts › resolvePostLoginRedirect) plussrc/lib/analytics.test.tsfailing at file load (duplicate helper). Both fail on unmodifiedmain(merge artifacts from Harden auth redirects, server-supplied URLs, production config and PII scrubbing (#862-#865) #1034) and are untouched here.tsc -b: 105 errors, identical to base, none in files touched here.npm run build: passes once the pre-existing duplicateuseQueryimport infeatures/payments/PaymentResultScreen.tsxis removed (that repair is proposed separately in fix(a11y,api,config): resolve assigned issues #993 #997 #998 #999 #1047, not duplicated here).Out of scope
The other issues assigned here (
#992API route params,#994ERC20mint(),#996recharts) reference an Express API, Solidity contracts and a chart library that do not exist in this repository; I left a note on each rather than inventing those surfaces.closes #994
closes #996
closes #992
closes #1000