Files
reactbin/specs/009-login-rate-limiting/data-model.md
agatha 7a835d3172 Feat: Rate-limit login endpoint to block brute-force attacks
After LOGIN_MAX_FAILURES consecutive failed attempts from the same source
IP within LOGIN_WINDOW_SECONDS, POST /api/v1/auth/token returns HTTP 429
with a Retry-After header for LOGIN_COOLDOWN_SECONDS. A successful login
resets the counter. Trusted upstream proxy IPs/CIDRs can be declared via
LOGIN_TRUSTED_PROXY_IPS so X-Forwarded-For is honoured correctly behind
nginx ingress or similar reverse proxies.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 21:01:37 +00:00

2.6 KiB

Data Model: Login Brute-Force Protection

Overview

This feature introduces no new database tables. The only data entity is a transient, in-memory rate-limit record that does not survive process restarts. This is intentional (see research.md Decision 3).


Entity: Rate-Limit Record (in-memory only)

Field Type Description
failures int Count of consecutive failed login attempts in the current window
window_start float Unix timestamp marking when the current counting window began
blocked_until float Unix timestamp after which the source is no longer blocked; 0.0 if not blocked

Keyed by: resolved client IP address string (e.g., "192.168.1.1"); see get_client_ip() in rate_limiter.py for resolution logic

Lifecycle:

  1. Record is created on the first failed login from a source.
  2. failures increments on each subsequent failure within the window.
  3. When failures >= LOGIN_MAX_FAILURES, blocked_until is set to now + LOGIN_COOLDOWN_SECONDS.
  4. When blocked_until has passed, the record is deleted on the next request from that source.
  5. A successful login deletes the record immediately (failure counter reset).
  6. If now - window_start > LOGIN_WINDOW_SECONDS without triggering lockout, the counter resets within the existing record.

State machine:

[no record]
     │ first failure
     ▼
[tracking] ──── failure N ≥ max ────► [blocked]
     │                                     │
     │ success / window expires             │ cooldown expires
     ▼                                     ▼
[no record] ◄─────────────────────── [no record]

Configuration Entity: Rate-Limit Settings

Stored as environment variables; loaded via app.config.Settings:

Env Var Default Description
LOGIN_MAX_FAILURES 5 Failures within window before lockout
LOGIN_WINDOW_SECONDS 300 Rolling window duration in seconds (5 minutes)
LOGIN_COOLDOWN_SECONDS 900 Lockout duration in seconds after threshold exceeded (15 minutes)
LOGIN_TRUSTED_PROXY_IPS "" Comma-separated IPs/CIDRs of trusted upstream proxies (e.g., 10.0.0.0/8); empty = disabled