From b5630d2af7aafc49b9bc4d740518c26aa8ac9713 Mon Sep 17 00:00:00 2001 From: Mikhail Romanko Date: Wed, 21 Oct 2026 20:28:50 +0300 Subject: [PATCH] Fix remote IP, fix title --- backend/internal/auth/handler.go | 23 +++++++++++++++++++++++ backend/internal/rest/context.go | 9 --------- backend/internal/rest/handler_auth.go | 4 ++-- docker/nginx.conf | 8 +++++++- frontend/index.html | 2 +- frontend/nginx.conf.example | 7 ++++++- 6 files changed, 39 insertions(+), 14 deletions(-) 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 @@ - goChat + uPortal
diff --git a/frontend/nginx.conf.example b/frontend/nginx.conf.example index 16da40b..5161ff8 100644 --- a/frontend/nginx.conf.example +++ b/frontend/nginx.conf.example @@ -26,12 +26,17 @@ server { # Reverse proxy the API to the Go service. Keeping the same origin means # the session cookie (SameSite=Lax) and CSRF cookie work without CORS. + # + # X-Forwarded-For uses $remote_addr (the immediate peer) so a client cannot + # spoof its recorded session IP; the backend trusts the leftmost value. If + # this nginx sits behind another trusted proxy, use $proxy_add_x_forwarded_for + # and make the backend read the appropriate hop instead. location /api/ { proxy_pass http://127.0.0.1:8080; 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; } }