Skip to content
This repository was archived by the owner on Oct 5, 2026. It is now read-only.
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

Closed

Description

@ParallelEntrepreneur

Part of #1.

Blocked by: #5 (D1 schema: users, identities, credentials, sessions, single-use tokens, clients)

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

Activity

  1. ParallelEntrepreneur commented on Sep 7, 2026

    @ParallelEntrepreneur
    ContributorAuthor

    Most of this is shipped; leaving it open for the parts I could not verify.

    Shipped (#42 for password, #43 for magic links):

    • 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    authshared authentication servicesecuritytouches credentials or token issuing

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions