"""Client IP extraction behind a reverse proxy. Prod runs behind IIS ARR, which sets X-Forwarded-For to "clientip:port" (and appends a hop per proxy). The trailing source port is ephemeral - it changes every connection - so it must be stripped before the value is stored (audit), matched (dashboard IP fallback), or used as a rate-limit key (login). Left in, it makes every request look like a different client. X-Forwarded-For is NOT read here. A client can send that header itself, so trusting it unconditionally hands any caller a free identity: the login rate limit and the account lockout are keyed on this value, and the audit log records it. Reading it directly also walked around TRUSTED_PROXY_COUNT, which exists precisely to decide whether a proxy is really in front (see the ProxyFix block in shopdb/__init__.py). The trusted case is already handled upstream. When TRUSTED_PROXY_COUNT is set, ProxyFix resolves X-Forwarded-For into remote_addr before the request reaches a view, so remote_addr is the real client. When it is unset, remote_addr is the peer that actually connected. Either way remote_addr is the trustworthy source, which is why this module only strips the port from it. A site behind IIS or nginx MUST set TRUSTED_PROXY_COUNT (1 for a single proxy), or every request will key on the proxy's own address and the per-client rate limit becomes one shared bucket. Port stripping is IPv6-aware: a bracketed [2001:db8::1]:443 loses its port; a bare 2001:db8::1 (many colons, no brackets) is returned untouched. """ def strip_port(ipaddress): """Return ipaddress without a trailing :port. IPv6-safe. None/empty-safe.""" if not ipaddress: return ipaddress ipaddress = ipaddress.strip() if ipaddress.startswith('['): # [IPv6] or [IPv6]:port -> the address inside the brackets return ipaddress[1:].split(']', 1)[0] if ipaddress.count(':') == 1: # exactly one colon = IPv4:port (a bare IPv6 has several colons) return ipaddress.split(':', 1)[0] return ipaddress def client_ip(request_obj): """Client IP, port stripped, from the connecting peer. Resolved from remote_addr, which ProxyFix has already rewritten from X-Forwarded-For when TRUSTED_PROXY_COUNT says a proxy is really in front. Returns None only when remote_addr is absent (callers that need a non-null key coerce it). """ return strip_port(request_obj.remote_addr)