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:
parent
36572d99f3
commit
f608931779
1 changed files with 23 additions and 0 deletions
23
src/lib/http/client-ip.ts
Normal file
23
src/lib/http/client-ip.ts
Normal 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";
|
||||
}
|
||||
Loading…
Reference in a new issue