# 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 |