Repository navigation
Conversation
Sign-in code and magic-link sends now run only when every applicable firewall rule answers that the caller may proceed. The two send-budget checks go through the shared checkFirewallRateLimit helper, so a rule that is not configured or that cannot be evaluated is refused and reported through the shared per-rule reporter. The refusals keep the existing 429 SIGNUP-RATE-LIMITED response.
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Executive SummaryThis three-file change routes both sign-in email send-budget checks through the existing Assumptions: Files Reviewed (3 files)
Reviewed by deepseek-flash-latest · Input: 54.8K · Output: 11.6K · Cached: 739.6K Review guidance: REVIEW.md from base branch |
eshurakov
left a comment
There was a problem hiding this comment.
Bot - Fail-closed behavior is correct, but confirm both the magic-link-email and sign-in-code-email-ip Vercel Firewall rules are live in the production project before merge (needs:prod-action); without them, every email sign-in and magic-link send returns 429.
|
bot: On the approving review remark — confirm both the The dependency is recorded as the merge prerequisite rather than resolved here. The No code change follows from the remark: the refusal path is the owner's recorded decision and the client-visible contract is unchanged ( |
|
bot: Update on the approving review remark — both rules answer in production. Method (at head 49e7d19, from the production project's Sentry data, not from the deployment):
What I could not verify from here, and why No code change follows from the remark: the refusal path is the owner's recorded decision and the client-visible contract is unchanged ( |
Evidence
Evidence: demonstrated — the native email sign-in route ran as its real module in
NODE_ENV=production, with the installed@vercel/firewall@1.2.1client performing its HTTPS rate-limit lookup against a local stand-in that implements the documented answer protocol, and with real reads and writes on the local dev Postgres, at head49e7d1928.Observed contract at that head:
200 {"success":true,"challengeId":"…"}429 {"success":false,"error":"SIGNUP-RATE-LIMITED"}429 {"success":false,"error":"SIGNUP-RATE-LIMITED"}429 {"success":false,"error":"SIGNUP-RATE-LIMITED"}Every row went through the same route module. The stand-in recorded each firewall check with its rule id and the per-address or per-caller key prefix (
magic-link-email:…,sign-in-code-email-ip:…), so each response is attributable to the send-budget check. Rows created by the permitting scenario were deleted afterwards.Method and limitations: a route-module run, not a
next build/next startserver or a browser session. Mail delivery was suppressed by the automated-test switch and NeverBounce was unconfigured, so no external request was made. No Sentry DSN exists on this VM, so report emission is proven by unit assertions rather than by a delivered event. The production firewall configuration cannot be read from this VM.Unit and static checks:
src/lib/auth,src/app/api/auth/native/otp,src/app/api/auth/magic-link,src/lib/observability/rest-routes-auth.test.ts: 32 suites, 531 tests passed. The send-budget suite (20 tests) pins both rules permitting, a rule that answers without permitting, a rule that does not answer, an exhausted budget, key derivation, and one report per rule per interval.pnpm --filter web typecheck: exit 0.oxlint --config .oxlintrc.jsonon the changed files: clean.oxfmt --checkon the changed files: clean.Change
apps/web/src/lib/auth/email-signin-eligibility.tsruns both send-budget checks through the sharedcheckFirewallRateLimithelper and refuses the send unless each answers that the caller may proceed.magic-link-emailcounts per normalized address andsign-in-code-email-ipcounts per caller address; the per-address check runs first so a refusal spends no further allowance. The shared helper reports an unconfigured or unevaluable rule once per rule per minute, replacing the local reporter. The response contract is unchanged:429 SIGNUP-RATE-LIMITEDwith the existing body, so no client-visible error code or status changes.ENVIRONMENT.mdnow states that both rules are required configuration and that the send runs only when both answer.Production prerequisites
magic-link-email, counted per normalized address, andsign-in-code-email-ip, counted per caller address. Each endpoint send requires both answers, so both must exist.Proxy answers
The owner's proxy gave these answers in the owner's place.
Answer: Yes, refuse the send
Confidence: 0.50 · Precedent: Conflicts in part with 'If a protective guard's configuration is missing, keep serving requests and report the gap through an alert' (fix(ai-gateway): refuse inference the account cap cannot enforce #8222 closed). The work item names later owner decisions on the analogous native sign-in rules (PRs fix(web): require an answering address rate limit on native sign-in #9409, fix(web): issue native admission challenges only on a permitting limiter answer #9617) that fail closed. Email sends cost money and are an abuse path, so refuse, keep the Sentry alert, and make the rules a production prerequisite.
Answer: Keep 429 SIGNUP-RATE-LIMITED
Confidence: 0.85 · Precedent: 'Close a PR that breaks an existing client contract, such as Kilo CLI or VS Code sign-in' (refactor(device-auth): poll sign-in status only through the token endpoint #8038 closed)
Answer: Reuse checkFirewallRateLimit
Confidence: 0.80 · Precedent: 'Prefer existing libraries to hand-rolled code' and 'Replace old paths instead of keeping them alongside' ("alongside? why not instead?")
Answer: Yes, list both rules
Confidence: 0.85 · Precedent: 'Label a PR that needs a production action before merge … with the action-needed label from the work session's own report … do the action yourself when you can and then merge' (fabebdc394c9, Vercel rules set up, then merge)