diff --git a/backend/internal/auth/handler.go b/backend/internal/auth/handler.go index c0bed74..161daae 100644 --- a/backend/internal/auth/handler.go +++ b/backend/internal/auth/handler.go @@ -1,10 +1,33 @@ package auth import ( + "net" "net/http" + "strings" "time" ) +// ExtractIP resolves the client's IP address from a request, accounting for the +// reverse proxy in front of the backend. When the proxy forwards the original +// address (X-Forwarded-For / X-Real-IP) the leftmost value is used; otherwise it +// falls back to the connection's remote address. +func ExtractIP(r *http.Request) string { + if xff := r.Header.Get("X-Forwarded-For"); xff != "" { + ips := strings.Split(xff, ",") + return strings.TrimSpace(ips[0]) + } + + if realIP := r.Header.Get("X-Real-IP"); realIP != "" { + return realIP + } + + ip, _, err := net.SplitHostPort(r.RemoteAddr) + if err != nil { + return r.RemoteAddr + } + return ip +} + func SigninSetup(w http.ResponseWriter, cookieName, cookieValue string, expiresAt time.Time, secure bool) { maxAge := expiresAt.Sub(time.Now().UTC()) http.SetCookie(w, &http.Cookie{ diff --git a/backend/internal/rest/context.go b/backend/internal/rest/context.go index 6ea79eb..8e67594 100644 --- a/backend/internal/rest/context.go +++ b/backend/internal/rest/context.go @@ -2,7 +2,6 @@ package rest import ( "fmt" - "net" "net/http" "goAuth/internal/auth" @@ -27,14 +26,6 @@ func sessionFromRequest(r *http.Request) *auth.Session { return session } -func clientAddress(r *http.Request) string { - host, _, err := net.SplitHostPort(r.RemoteAddr) - if err != nil { - return r.RemoteAddr - } - return host -} - func formatUserId(id int64) string { return fmt.Sprintf("user-%d", id) } diff --git a/backend/internal/rest/handler_auth.go b/backend/internal/rest/handler_auth.go index 286549f..d525906 100644 --- a/backend/internal/rest/handler_auth.go +++ b/backend/internal/rest/handler_auth.go @@ -39,7 +39,7 @@ func (h *Handler) signup(w http.ResponseWriter, r *http.Request) { return } - address := clientAddress(r) + address := auth.ExtractIP(r) device := h.sessions.ClassifiedUserAgent(r.UserAgent()) u, session, err := h.sessions.RegisterUser(r.Context(), req.Login, req.Password, req.Invite, address, device) @@ -64,7 +64,7 @@ func (h *Handler) signin(w http.ResponseWriter, r *http.Request) { return } - address := clientAddress(r) + address := auth.ExtractIP(r) device := h.sessions.ClassifiedUserAgent(r.UserAgent()) u, session, err := h.sessions.LoginUser(r.Context(), req.Login, req.Password, address, device, req.Remember) diff --git a/docker/nginx.conf b/docker/nginx.conf index 07db818..40f9a17 100644 --- a/docker/nginx.conf +++ b/docker/nginx.conf @@ -22,12 +22,18 @@ server { } # Proxy the API to the Go backend running alongside nginx. + # + # X-Forwarded-For is set to $remote_addr (the peer nginx actually saw), NOT + # $proxy_add_x_forwarded_for. The latter appends to any client-supplied + # header, which would let a client spoof its own IP since the backend trusts + # the leftmost value. If you deploy behind another trusted proxy, switch to + # $proxy_add_x_forwarded_for and make the backend read the correct hop. location /api/ { proxy_pass http://${BACKEND_HOST}:${BACKEND_PORT}; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; } } diff --git a/frontend/index.html b/frontend/index.html index ad40227..fe93f60 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -3,7 +3,7 @@
-