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>
This commit is contained in:
2026-05-06 21:01:37 +00:00
parent f3e0021ee8
commit 7a835d3172
18 changed files with 1320 additions and 7 deletions

View File

@@ -0,0 +1,85 @@
# API Contract: Authentication
## POST /api/v1/auth/token
Authenticates the owner and returns a JWT access token.
**This endpoint is modified by feature 009** to enforce brute-force protection.
All previous behaviour is preserved. One new response code (429) is added.
### Request
```
POST /api/v1/auth/token
Content-Type: application/json
```
```json
{
"username": "string",
"password": "string"
}
```
### Responses
#### 200 OK — Credentials accepted
```json
{
"access_token": "<jwt>",
"token_type": "bearer",
"expires_in": 86400
}
```
Side effect: resets the failure counter for the caller's IP address.
---
#### 401 Unauthorized — Credentials rejected
```json
{
"detail": "Invalid credentials",
"code": "invalid_credentials"
}
```
Side effect: increments the failure counter for the caller's IP address. If the
counter reaches `LOGIN_MAX_FAILURES`, subsequent requests from this IP will receive
429 until the cooldown expires.
---
#### 429 Too Many Requests — Source blocked after repeated failures
**This response is new in feature 009.**
```
HTTP/1.1 429 Too Many Requests
Retry-After: 900
Content-Type: application/json
```
```json
{
"detail": "Too many failed login attempts. Please try again later.",
"code": "login_rate_limited"
}
```
The `Retry-After` header value is the configured cooldown duration in seconds (default: 900).
It reflects the maximum possible wait, not the exact remaining lockout time.
No credentials are verified when this response is returned — the request is
rejected before authentication is attempted.
---
### Notes
- The failure counter is per source IP address (TCP peer, not forwarded headers).
- Threshold values (`LOGIN_MAX_FAILURES`, `LOGIN_WINDOW_SECONDS`, `LOGIN_COOLDOWN_SECONDS`)
are not disclosed in any response.
- Counters are in-memory and reset on process restart.