Files
plm-lottery/app/api/client_ip.py
T

32 lines
1.6 KiB
Python
Raw Normal View History

from fastapi import Request
def client_ip(request: Request) -> str:
"""The caller's real IP, from Caddy's X-Forwarded-For (see Caddyfile) —
request.client.host would otherwise be the reverse proxy's own address, not
the caller's. Falls back to request.client.host only if the header is
somehow missing (e.g. the app container hit directly, bypassing Caddy).
Shared by the login/registration throttles (B-33) and the SSE per-IP
subscriber cap (B-38) so the two can't drift into different notions of
"the client's IP".
B-54: the *last* element, not the first. A proxy appends the address it saw
to any X-Forwarded-For the client already sent, so the first element is
attacker-controlled — with the header read from the front, rotating a fake
value per request gave every request a fresh identity and turned all three
IP-keyed controls above into decoration. The last element is the one written
by the hop closest to us, i.e. by our own proxy. Exactly one trusted proxy
sits in front of this app (Caddy, see docker-compose.yml, where `app` is
only `expose`d on the compose network and never published to the host), so
the last element is the real peer. The Caddyfile now also overwrites the
header with `header_up X-Forwarded-For {remote_host}`, which collapses it to
a single value — belt and braces: either fix alone closes B-54.
"""
forwarded = request.headers.get("x-forwarded-for")
if forwarded:
hops = [hop.strip() for hop in forwarded.split(",") if hop.strip()]
if hops:
return hops[-1]
return request.client.host if request.client else "unknown"