From cc155214e01c1f02cc17f6b01208dcf67dee9468 Mon Sep 17 00:00:00 2001 From: Julien Gautier Date: Sun, 26 Apr 2026 20:54:26 +0200 Subject: [PATCH] docs(adr): add ADR 0010 (CORS restriction) and 0011 (auth rate limiting) --- .../decisions/0010-cors-origin-restriction.md | 52 ++++++++++++++++ .../0011-rate-limiting-auth-endpoints.md | 60 +++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 docs/decisions/0010-cors-origin-restriction.md create mode 100644 docs/decisions/0011-rate-limiting-auth-endpoints.md diff --git a/docs/decisions/0010-cors-origin-restriction.md b/docs/decisions/0010-cors-origin-restriction.md new file mode 100644 index 0000000..2b6068b --- /dev/null +++ b/docs/decisions/0010-cors-origin-restriction.md @@ -0,0 +1,52 @@ +# Restrict CORS to explicit allowed origins + +* Status: accepted +* Date: 2026-04-26 + +## Context and Problem Statement + +The API serves an Angular frontend from a different origin. Cross-Origin Resource Sharing (CORS) must be configured to allow the frontend to call the API. The initial configuration used `cors()` with no options, which sets `Access-Control-Allow-Origin: *` and allows any website to make cross-origin requests to the API — including requests carrying a victim's JWT token. + +## Decision Drivers + +* Any malicious website could make authenticated requests to the API on behalf of a logged-in user (CSRF-like attack via CORS misconfiguration). +* The list of legitimate frontend origins is finite and known at deployment time. +* The `Authorization: Token ` header is a non-simple header that always triggers a CORS preflight — the server controls which origins receive a valid preflight response. + +## Considered Options + +* Wildcard CORS (`Access-Control-Allow-Origin: *`) +* Explicit origin allowlist via environment variable + +## Decision Outcome + +Chosen option: "Explicit origin allowlist", because it limits cross-origin access to known, trusted frontends. Unknown origins receive no CORS headers and are blocked by the browser. + +Allowed origins are read from the `ALLOWED_ORIGINS` environment variable as a comma-separated list. If the variable is not set, all cross-origin requests are rejected (`origin: false`). `credentials: true` is set to allow the `Authorization` header to be sent cross-origin. + +### Positive Consequences + +* Malicious third-party sites cannot make authenticated cross-origin requests on behalf of users. +* Adding a new frontend origin (e.g. a mobile web app) requires only an env var change, no code deployment. + +### Negative Consequences + +* `ALLOWED_ORIGINS` must be set correctly in every environment (dev, staging, prod) or the frontend will fail with CORS errors. Incorrect configuration is a common operational mistake. + +## Pros and Cons of the Options + +### Wildcard CORS + +* Good, because no configuration required. +* Bad, because allows any origin — malicious sites can make authenticated requests on behalf of logged-in users. +* Bad, because incompatible with `credentials: true` per the CORS spec when `Access-Control-Allow-Origin` is `*`. + +### Explicit origin allowlist + +* Good, because only known frontends can make cross-origin requests. +* Good, because configurable per environment without code changes. +* Bad, because requires correct `ALLOWED_ORIGINS` configuration in each environment. + +## Links + +* Related to [ADR 0004](0004-route-organisation-by-domain.md) — all API routes are under `/api/*`, making CORS the only cross-origin entry point. diff --git a/docs/decisions/0011-rate-limiting-auth-endpoints.md b/docs/decisions/0011-rate-limiting-auth-endpoints.md new file mode 100644 index 0000000..348477a --- /dev/null +++ b/docs/decisions/0011-rate-limiting-auth-endpoints.md @@ -0,0 +1,60 @@ +# Apply rate limiting to authentication endpoints + +* Status: accepted +* Date: 2026-04-26 + +## Context and Problem Statement + +Authentication endpoints (`POST /login`, `POST /` registration) were exposed without any request throttling. An attacker could perform unlimited credential stuffing, dictionary attacks, or account enumeration against these endpoints with no server-side resistance. + +## Decision Drivers + +* Login and registration are the highest-value targets for automated attacks. +* No existing infrastructure (WAF, reverse-proxy rate limiting) in front of the API provides this protection at the application layer. +* The rate limit must be permissive enough not to affect legitimate users while blocking automated attack patterns. + +## Considered Options + +* No rate limiting (previous state) +* Global rate limiting on all routes +* Targeted rate limiting on authentication endpoints only + +## Decision Outcome + +Chosen option: "Targeted rate limiting on authentication endpoints", because it provides strong protection where it matters most without risking false positives on data-intensive routes (e.g. Hero Wars analytics endpoints that may legitimately send many requests in a short window). + +Implementation: `express-rate-limit` middleware with a 10-request / 15-minute window per IP, applied to `POST /api/cms/user/login` and `POST /api/cms/user/` (registration). Returns HTTP 429 with a structured error body on limit exceeded. + +### Positive Consequences + +* Brute force and credential stuffing attacks are throttled to 10 attempts per 15 minutes per IP. +* Legitimate users (at most a few login attempts per session) are unaffected. + +### Negative Consequences + +* IP-based rate limiting can be bypassed by attackers rotating IPs or using proxies. +* Shared NAT environments (office networks, VPNs) could hit the limit if multiple users attempt to log in simultaneously from the same IP. The 10-request window is generous enough to make this unlikely in practice. +* If the API is ever placed behind a reverse proxy, `trust proxy` must be configured in Express so that the correct client IP is used rather than the proxy IP. + +## Pros and Cons of the Options + +### No rate limiting + +* Good, because no false positives. +* Bad, because unlimited brute force attacks possible. + +### Global rate limiting + +* Good, because protects all endpoints uniformly. +* Bad, because risks throttling legitimate use of analytics or data sync endpoints. + +### Targeted rate limiting on auth endpoints + +* Good, because protects the highest-risk endpoints without affecting others. +* Good, because easy to tune per endpoint independently. +* Bad, because other endpoints (e.g. password reset, if added later) must be manually included. + +## Links + +* Related to [ADR 0006](0006-jwt-authentication.md) — rate limiting protects the JWT issuance endpoint. +* Related to [ADR 0008](0008-hmac-sha256-api-key-hashing.md) — API key auth endpoint is protected by the deterministic hash lookup rather than rate limiting.