You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Oct 5, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Oct 5, 2026. It is now read-only.
Rate limiting and lockout on password and magic-link endpoints #12
Problem. Password login is a credential-stuffing target and magic-link requests are a mail-bombing target. Both need limits keyed on more than the client IP.
Proposed approach. Through the harness RateLimiter port and rate_limit_keys helper:
Password login: per IP (10 per minute), per normalized email (5 per 15 minutes regardless of IP), and a per-account lockout after 10 failures in an hour that requires a magic link or passkey to clear. Failed and successful attempts count identically toward the IP limit so success does not reveal anything.
Magic-link request: per IP (5 per 10 minutes) and per email (3 per hour); the response is identical whether or not the email exists.
Password registration and password change: per IP.
Captcha required on password login and magic-link request when the port is present, and fz doctor already refuses production without it.
All limits are configuration with the defaults above; responses are 429 with Retry-After.
Acceptance criteria
Each limit tested with the fake rate limiter; lockout tested with the sqlite adapter
Identical response bodies for existing and unknown emails on every limited endpoint
A locked account cannot log in with a correct password until cleared, and clearing is tested
Limits documented with the reasoning for each number
Both endpoints call cratefield_core::rate_limit_keys(ip, email) and answer 429 with Retry-After via cratefield_core::rate_limited(decision.retry_after) — auth-password/src/handlers.rs:97 and auth-magic-link/src/handlers.rs:106.
RateLimiter and Captcha are declared optional ports on both modules, so a deployment without them degrades rather than fails.
The per-account lockout lives in auth-password/src/lockout.rs, tested against the sqlite adapter.
Identical responses for existing and unknown addresses, with a real generated dummy hash so the timing does not differ either.
Not verified, and the reason this stays open:
The specific numbers in the proposal (10/min per IP, 5/15min per email, 3/hour for magic-link requests) are not asserted anywhere. The limits come from the harness's RateLimiter port and its configuration, so this repository does not choose or test them.
The last acceptance box — "limits documented with the reasoning for each number" — is therefore unmet.
Either close this as delivered and open a smaller issue for the numbers, or keep it open until the harness side is pinned down. I did not want to close it on my own reading.
Activity
Email and password: argon2id, a lockout, and answers that reveal noth…
ParallelEntrepreneur commentedon Sep 7, 2026
Most of this is shipped; leaving it open for the parts I could not verify.
Shipped (#42 for password, #43 for magic links):
cratefield_core::rate_limit_keys(ip, email)and answer429withRetry-Afterviacratefield_core::rate_limited(decision.retry_after)—auth-password/src/handlers.rs:97andauth-magic-link/src/handlers.rs:106.RateLimiterandCaptchaare declared optional ports on both modules, so a deployment without them degrades rather than fails.auth-password/src/lockout.rs, tested against the sqlite adapter.Not verified, and the reason this stays open:
RateLimiterport and its configuration, so this repository does not choose or test them.Either close this as delivered and open a smaller issue for the numbers, or keep it open until the harness side is pinned down. I did not want to close it on my own reading.