feat: explorer API, wallet, CCIP scripts, and config refresh
- 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
This commit is contained in:
111
backend/libs/go-http-middleware/client_ip.go
Normal file
111
backend/libs/go-http-middleware/client_ip.go
Normal file
@@ -0,0 +1,111 @@
|
||||
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
|
||||
}
|
||||
31
backend/libs/go-http-middleware/client_ip_test.go
Normal file
31
backend/libs/go-http-middleware/client_ip_test.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package httpmiddleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestClientIPFallsBackToRemoteAddrWhenProxyIsUntrusted(t *testing.T) {
|
||||
t.Setenv("TRUST_PROXY_IPS", "")
|
||||
t.Setenv("TRUST_PROXY_CIDRS", "")
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.RemoteAddr = "10.0.0.10:8443"
|
||||
req.Header.Set("X-Forwarded-For", "203.0.113.9, 10.0.0.10")
|
||||
|
||||
require.Equal(t, "10.0.0.10", ClientIP(req))
|
||||
}
|
||||
|
||||
func TestClientIPUsesForwardedHeadersFromTrustedProxy(t *testing.T) {
|
||||
t.Setenv("TRUST_PROXY_IPS", "")
|
||||
t.Setenv("TRUST_PROXY_CIDRS", "10.0.0.0/8")
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.RemoteAddr = "10.0.0.10:8443"
|
||||
req.Header.Set("X-Forwarded-For", "203.0.113.9, 10.0.0.10")
|
||||
|
||||
require.Equal(t, "203.0.113.9", ClientIP(req))
|
||||
}
|
||||
Reference in New Issue
Block a user