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