- Backend REST/gateway/track routes, analytics, Blockscout proxy paths. - Frontend wallet and liquidity surfaces; MetaMask token list alignment. - Deployment docs, verification scripts, address inventory updates. Check: go build ./... under backend/ (pass). Made-with: Cursor
112 lines
2.1 KiB
Go
112 lines
2.1 KiB
Go
package httpmiddleware
|
|
|
|
import (
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
// ClientIP returns the best-known client IP for a request.
|
|
//
|
|
// Forwarded headers are only trusted when the immediate remote address belongs
|
|
// to an explicitly trusted proxy listed in TRUST_PROXY_IPS and/or
|
|
// TRUST_PROXY_CIDRS.
|
|
func ClientIP(r *http.Request) string {
|
|
remoteIP := parseRemoteIP(r.RemoteAddr)
|
|
if remoteIP == "" {
|
|
remoteIP = strings.TrimSpace(r.RemoteAddr)
|
|
}
|
|
|
|
if !isTrustedProxy(remoteIP) {
|
|
return remoteIP
|
|
}
|
|
|
|
if forwarded := forwardedClientIP(r); forwarded != "" {
|
|
return forwarded
|
|
}
|
|
|
|
return remoteIP
|
|
}
|
|
|
|
func parseRemoteIP(raw string) string {
|
|
trimmed := strings.TrimSpace(raw)
|
|
if trimmed == "" {
|
|
return ""
|
|
}
|
|
|
|
if host, _, err := net.SplitHostPort(trimmed); err == nil {
|
|
return host
|
|
}
|
|
|
|
if ip := net.ParseIP(trimmed); ip != nil {
|
|
return ip.String()
|
|
}
|
|
|
|
return trimmed
|
|
}
|
|
|
|
func forwardedClientIP(r *http.Request) string {
|
|
for _, header := range []string{"X-Forwarded-For", "X-Real-IP"} {
|
|
raw := strings.TrimSpace(r.Header.Get(header))
|
|
if raw == "" {
|
|
continue
|
|
}
|
|
|
|
if header == "X-Forwarded-For" {
|
|
for _, part := range strings.Split(raw, ",") {
|
|
candidate := strings.TrimSpace(part)
|
|
if ip := net.ParseIP(candidate); ip != nil {
|
|
return ip.String()
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
|
|
if ip := net.ParseIP(raw); ip != nil {
|
|
return ip.String()
|
|
}
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
func isTrustedProxy(remoteIP string) bool {
|
|
ip := net.ParseIP(strings.TrimSpace(remoteIP))
|
|
if ip == nil {
|
|
return false
|
|
}
|
|
|
|
for _, exact := range splitEnvList("TRUST_PROXY_IPS") {
|
|
if trusted := net.ParseIP(exact); trusted != nil && trusted.Equal(ip) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
for _, cidr := range splitEnvList("TRUST_PROXY_CIDRS") {
|
|
_, network, err := net.ParseCIDR(cidr)
|
|
if err == nil && network.Contains(ip) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func splitEnvList(key string) []string {
|
|
raw := strings.TrimSpace(os.Getenv(key))
|
|
if raw == "" {
|
|
return nil
|
|
}
|
|
|
|
parts := strings.Split(raw, ",")
|
|
values := make([]string, 0, len(parts))
|
|
for _, part := range parts {
|
|
trimmed := strings.TrimSpace(part)
|
|
if trimmed != "" {
|
|
values = append(values, trimmed)
|
|
}
|
|
}
|
|
return values
|
|
}
|