feat(http): best-effort client IP from headers

Phase 8a Task 3. Reads x-forwarded-for (first hop), then x-real-ip,
falls back to 'unknown'. Not used for auth or logging — pure rate-
limit keying so concurrent clients do not share one bucket.
This commit is contained in:
Dave 2026-09-21 01:58:06 +02:00
parent 36572d99f3
commit f608931779

23
src/lib/http/client-ip.ts Normal file
View file

@ -0,0 +1,23 @@
/**
* Best-effort client IP for rate-limit keying.
*
* This is NOT used for authentication, logging, or any decision that needs to
* be accurate. It is a tie-breaker so two clients hammering the same token
* from different networks each get their own budget. The spec deliberately
* does not persist it.
*
* Reads `x-forwarded-for` first (the only header a load balancer or reverse
* proxy should be trusted to set), then `x-real-ip`, and falls back to
* "unknown" so all unrecognised callers share one bucket rather than each
* getting their own unlimited budget.
*/
export function clientIpFromHeaders(headers: Headers): string {
const fwd = headers.get("x-forwarded-for");
if (fwd) {
const first = fwd.split(",")[0]?.trim();
if (first) return first;
}
const real = headers.get("x-real-ip");
if (real) return real.trim();
return "unknown";
}