Files
ResolveSpec/pkg/security/requestinfo_test.go
T
Hein fbfc1aadb5 feat(security): send request info as login claims
Add RequestInfoMiddleware / WithRequestInfo, which put client details in the
context. Login, LoginWithAPIKey, LoginWithPasskey and JWT login merge them into
the claims: ip_address, client_port, is_private_ip, previous_session, device_id,
client_id, user_agent, server_ip_address, server_port, host, origin, referer.
RequestInfoOptions adds trusted-proxy handling for X-Forwarded-For/X-Real-IP.
2026-10-12 12:18:23 +02:00

71 lines
2.6 KiB
Go

package security
import (
"context"
"net"
"net/http"
"net/http/httptest"
"testing"
)
func TestEnrichClaims(t *testing.T) {
r := httptest.NewRequest("POST", "/login", nil)
r.RemoteAddr = "10.0.0.1:5555"
r.Header.Set("X-Forwarded-For", "1.2.3.4, 10.0.0.1")
r.Header.Set("Authorization", "Bearer old-token")
r.Header.Set("User-Agent", "ua/1")
r.Header.Set("Origin", "https://app.example")
r.Host = "api.example"
r = r.WithContext(context.WithValue(r.Context(), http.LocalAddrContextKey, &net.TCPAddr{IP: net.ParseIP("192.168.1.5"), Port: 8080}))
ctx := WithRequestInfo(r.Context(), r)
in := map[string]any{ClaimUserAgent: "x"}
got := enrichClaims(ctx, in)
if got[ClaimIPAddress] != "1.2.3.4" || got[ClaimPreviousSession] != "old-token" || got[ClaimUserAgent] != "x" ||
got[ClaimServerIPAddress] != "192.168.1.5" || got[ClaimHost] != "api.example" || got[ClaimOrigin] != "https://app.example" {
t.Fatalf("unexpected claims: %v", got)
}
if _, ok := in[ClaimIPAddress]; ok {
t.Fatal("input map mutated")
}
if got := enrichClaims(ctx, map[string]any{ClaimIPAddress: "9.9.9.9"}); got[ClaimIPAddress] != "9.9.9.9" {
t.Fatalf("caller value overridden: %v", got)
}
if got := enrichClaims(r.Context(), nil); got != nil {
t.Fatalf("expected nil without info, got %v", got)
}
}
func TestClientIPTrustedProxies(t *testing.T) {
mk := func(peer, xff string) *http.Request {
r := httptest.NewRequest("GET", "/", nil)
r.RemoteAddr = peer
if xff != "" {
r.Header.Set("X-Forwarded-For", xff)
}
return r
}
o := RequestInfoOptions{TrustedProxies: []string{"10.0.0.0/8", "172.16.0.1"}}
if got := clientIP(mk("10.0.0.1:1", "6.6.6.6, 1.2.3.4, 10.0.0.2"), o); got != "1.2.3.4" {
t.Fatalf("spoofed prefix not skipped: %s", got)
}
if got := clientIP(mk("8.8.8.8:1", "1.2.3.4"), o); got != "8.8.8.8" {
t.Fatalf("untrusted peer header honoured: %s", got)
}
if got := clientIP(mk("10.0.0.1:1", "1.2.3.4"), RequestInfoOptions{IgnoreProxyHeaders: true}); got != "10.0.0.1" {
t.Fatalf("IgnoreProxyHeaders: %s", got)
}
}
func TestRequestInfoExtras(t *testing.T) {
r := httptest.NewRequest("POST", "/login", nil)
r.RemoteAddr = "192.168.0.7:4242"
r.Header.Set("X-Device-ID", "dev-1")
r.Header.Set("X-Client-ID", "app-9")
r = r.WithContext(context.WithValue(r.Context(), http.LocalAddrContextKey, &net.TCPAddr{IP: net.ParseIP("10.1.1.1"), Port: 8443}))
got := enrichClaims(WithRequestInfo(r.Context(), r), nil)
if got[ClaimClientPort] != "4242" || got[ClaimServerPort] != "8443" || got[ClaimDeviceID] != "dev-1" || got[ClaimClientID] != "app-9" || got[ClaimIsPrivateIP] != true {
t.Fatalf("unexpected claims: %v", got)
}
}