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>
2.6 KiB
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:
- Record is created on the first failed login from a source.
failuresincrements on each subsequent failure within the window.- When
failures >= LOGIN_MAX_FAILURES,blocked_untilis set tonow + LOGIN_COOLDOWN_SECONDS. - When
blocked_untilhas passed, the record is deleted on the next request from that source. - A successful login deletes the record immediately (failure counter reset).
- If
now - window_start > LOGIN_WINDOW_SECONDSwithout 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 |