// Package clientip exposes the helper that resolves the originating client // IP for an inbound HTTP request. Backend trusts the value because the // network segment between gateway and backend is the trust boundary // (`ARCHITECTURE.md` ยง15-16): gateway is responsible for sanitising and // populating `X-Forwarded-For` before the request reaches backend. // // Both the public-auth handler chain (handlers_auth_helpers.go) and the // user-surface geo-counter middleware reuse the same extraction so the two // surfaces never disagree about the IP they record. package clientip import ( "net" "strings" "github.com/gin-gonic/gin" ) // ExtractSourceIP returns the originating client IP for the request behind // c. The leftmost entry of `X-Forwarded-For` is preferred; when the header // is absent or empty, the connection RemoteAddr is used (with the port // stripped). The empty string is returned when neither source yields a // usable value, which lets callers treat the result as "no IP available" // and skip dependent work. func ExtractSourceIP(c *gin.Context) string { if c == nil || c.Request == nil { return "" } if xff := c.GetHeader("X-Forwarded-For"); xff != "" { first := xff if idx := strings.IndexByte(first, ','); idx >= 0 { first = first[:idx] } return strings.TrimSpace(first) } addr := c.Request.RemoteAddr if host, _, err := net.SplitHostPort(addr); err == nil { return host } return addr }