fear(client): extract client's IP and store in database and structured logs
This commit is contained in:
67
backend/internal/httpapi/client_ip.go
Normal file
67
backend/internal/httpapi/client_ip.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// extractClientIP extracts and validates the client's IP address from the HTTP request.
|
||||
// It inspects standard reverse proxy and CDN headers in priority order before falling
|
||||
// back to the remote socket address. Returns nil if no valid IP address can be found.
|
||||
func extractClientIP(r *http.Request) *string {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Priority headers commonly set by reverse proxies, load balancers, and CDNs.
|
||||
headers := []string{
|
||||
"CF-Connecting-IP", // Cloudflare
|
||||
"True-Client-IP", // Cloudflare Enterprise, Akamai
|
||||
"X-Real-IP", // Nginx, Traefik
|
||||
}
|
||||
|
||||
for _, header := range headers {
|
||||
if val := strings.TrimSpace(r.Header.Get(header)); val != "" {
|
||||
if ip := parseIP(val); ip != "" {
|
||||
return &ip
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// X-Forwarded-For can contain multiple comma-separated IPs: client, proxy1, proxy2.
|
||||
// The original client IP is the first (leftmost) entry.
|
||||
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
|
||||
for _, part := range strings.Split(xff, ",") {
|
||||
if ip := parseIP(part); ip != "" {
|
||||
return &ip
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to RemoteAddr (typically "host:port" or "[ipv6]:port").
|
||||
if r.RemoteAddr != "" {
|
||||
host, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||
if err != nil {
|
||||
host = r.RemoteAddr
|
||||
}
|
||||
if ip := parseIP(host); ip != "" {
|
||||
return &ip
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseIP(raw string) string {
|
||||
raw = strings.TrimSpace(raw)
|
||||
raw = strings.TrimPrefix(raw, "[")
|
||||
raw = strings.TrimSuffix(raw, "]")
|
||||
|
||||
ip := net.ParseIP(raw)
|
||||
if ip == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return ip.String()
|
||||
}
|
||||
Reference in New Issue
Block a user