Implement login endpoint rate limiting (TASK-007)

- Add in-memory rate limiter with per-IP deque tracking of attempt timestamps
- Limit login attempts to 5 per 60 seconds per IP, return 429 on excess
- Add Retry-After header to rate limit responses
- Implement IP extraction utility with proxy trust validation (prevent X-Forwarded-For spoofing)
- Integrate rate limiter into auth router and dependencies
- Add 10-second asyncio.sleep on failed login attempts to further slow brute-force
- Add comprehensive tests for rate limiting (9 new tests, all passing)
- Update Features.md to document login rate limiting
- Update Backend-Development.md with rate limiting conventions and design patterns
- Fix test infrastructure issues: update password to meet complexity requirements
- Fix TestValidateSession tests to use Bearer token authentication
- All tests passing: 23 auth tests + full test suite coverage

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-04-26 12:40:52 +02:00
parent 9725714aa2
commit ea4c7c2f85
9 changed files with 414 additions and 73 deletions

View File

@@ -522,9 +522,38 @@ environment:
**Important:** If `Secure=true` is set, browsers will reject the session cookie when the backend is served over HTTP. Ensure your nginx/reverse proxy terminates TLS and passes `X-Forwarded-Proto: https` so FastAPI knows the connection is secure.
### Login Rate Limiting
The login endpoint (`POST /api/auth/login`) is protected against brute-force attacks using an in-memory rate limiter.
**Design:**
- Uses a `dict[str, deque[float]]` keyed by client IP, storing login attempt timestamps within a time window.
- Attempts outside the window are automatically removed during validation checks.
- Expired IP entries are cleaned up to prevent unbounded memory growth.
**Rate Limit Rules:**
- **5 attempts per 60 seconds** per IP address.
- Requests exceeding the limit return **HTTP 429 Too Many Requests** with a `Retry-After` header.
- Each failed login triggers a 10-second server-side delay (`asyncio.sleep`) to further slow attacks, on top of bcrypt hashing (~100ms).
**IP Extraction (Proxy Safety):**
- When behind nginx, the rate limiter reads the real client IP from `X-Forwarded-For` or `X-Real-IP` headers.
- Only trusts these headers when the immediate connection source is in a configured trusted proxy list.
- Prevents attackers from spoofing these headers to bypass rate limits.
- Falls back to the direct connection IP when proxy headers cannot be trusted.
**Process-Local Limitation:**
- The rate limiter is process-local (in-memory). In multi-worker deployments (e.g., Gunicorn with 4 workers), each worker maintains its own rate limit counter.
- This is acceptable because the single-worker constraint is enforced elsewhere. See [TASK-002/003 notes](Instructions.md) for details.
**Implementation:**
- Rate limiter: `app.utils.rate_limiter.RateLimiter`
- IP extraction: `app.utils.client_ip.get_client_ip()`
- Dependency: `LoginRateLimiterDep` in `app.dependencies`
---
## 13. Git & Workflow
## 14. Git & Workflow
- **Branch naming:** `feature/<short-description>`, `fix/<short-description>`, `chore/<short-description>`.
- **Commit messages:** imperative tense, max 72 chars first line (`Add jail reload endpoint`, `Fix ban history query`).
@@ -534,7 +563,7 @@ environment:
---
## 14. Coding Principles
## 15. Coding Principles
These principles are **non-negotiable**. Every backend contributor must internalise and apply them daily.
@@ -756,7 +785,7 @@ To adopt a Redis backend:
---
## 15. Quick Reference — Do / Don't
## 16. Quick Reference — Do / Don't
| Do | Don't |
|---|---|