package auth import ( "net/http" "net/http/httptest" "net/netip" "testing" "git.noroutes.net/NoRoutes/vibetunes/internal/config" ) func mustPrefix(t *testing.T, s string) netip.Prefix { t.Helper() p, err := netip.ParsePrefix(s) if err != nil { t.Fatalf("ParsePrefix(%q): %v", s, err) } return p } func TestExtAuthTrustsConfiguredPeer(t *testing.T) { db := openTestDB(t) e := &ExtAuth{DB: db, Config: config.ExtAuthConfig{ Enabled: true, UserHeader: "Remote-User", TrustedSources: []netip.Prefix{mustPrefix(t, "127.0.0.1/32")}, }} req := httptest.NewRequest(http.MethodGet, "/", nil) req.RemoteAddr = "127.0.0.1:54321" req.Header.Set("Remote-User", "alice") if got := e.IdentityFromRequest(req); got != "alice" { t.Fatalf("IdentityFromRequest = %q, want alice", got) } } func TestExtAuthRejectsUntrustedPeer(t *testing.T) { db := openTestDB(t) e := &ExtAuth{DB: db, Config: config.ExtAuthConfig{ Enabled: true, UserHeader: "Remote-User", TrustedSources: []netip.Prefix{mustPrefix(t, "127.0.0.1/32")}, }} req := httptest.NewRequest(http.MethodGet, "/", nil) req.RemoteAddr = "203.0.113.7:54321" // arbitrary internet client, not the trusted proxy req.Header.Set("Remote-User", "alice") if got := e.IdentityFromRequest(req); got != "" { t.Fatalf("IdentityFromRequest = %q, want empty for untrusted peer", got) } } // TestExtAuthIgnoresForwardedHeadersFromPeer is the core anti-spoofing // guarantee: an arbitrary internet client cannot impersonate a trusted // proxy by setting X-Forwarded-For/X-Real-IP/Forwarded, because those // headers are never consulted to decide whether the peer is trusted — // only the real TCP RemoteAddr is. func TestExtAuthIgnoresForwardedHeadersFromPeer(t *testing.T) { db := openTestDB(t) e := &ExtAuth{DB: db, Config: config.ExtAuthConfig{ Enabled: true, UserHeader: "Remote-User", TrustedSources: []netip.Prefix{mustPrefix(t, "127.0.0.1/32")}, }} req := httptest.NewRequest(http.MethodGet, "/", nil) req.RemoteAddr = "203.0.113.7:54321" // real connection is from an untrusted internet host req.Header.Set("X-Forwarded-For", "127.0.0.1") req.Header.Set("X-Real-IP", "127.0.0.1") req.Header.Set("Forwarded", "for=127.0.0.1") req.Header.Set("Remote-User", "admin") if got := e.IdentityFromRequest(req); got != "" { t.Fatalf("IdentityFromRequest = %q, want empty: forwarded headers must not establish trust", got) } } func TestExtAuthDisabledNeverTrusts(t *testing.T) { db := openTestDB(t) e := &ExtAuth{DB: db, Config: config.ExtAuthConfig{ Enabled: false, UserHeader: "Remote-User", TrustedSources: []netip.Prefix{mustPrefix(t, "0.0.0.0/0")}, }} req := httptest.NewRequest(http.MethodGet, "/", nil) req.RemoteAddr = "127.0.0.1:1" req.Header.Set("Remote-User", "alice") if got := e.IdentityFromRequest(req); got != "" { t.Fatalf("IdentityFromRequest = %q, want empty when ExtAuth disabled", got) } } func TestExtAuthNoTrustedSourcesNeverTrusts(t *testing.T) { db := openTestDB(t) e := &ExtAuth{DB: db, Config: config.ExtAuthConfig{Enabled: true, UserHeader: "Remote-User"}} req := httptest.NewRequest(http.MethodGet, "/", nil) req.RemoteAddr = "127.0.0.1:1" req.Header.Set("Remote-User", "alice") if got := e.IdentityFromRequest(req); got != "" { t.Fatalf("IdentityFromRequest = %q, want empty with no trusted sources configured", got) } } func TestExtAuthResolveUserAutoCreates(t *testing.T) { db := openTestDB(t) e := &ExtAuth{DB: db, Config: config.ExtAuthConfig{AutoCreateUsers: true}} u, err := e.ResolveUser("newperson") if err != nil { t.Fatalf("ResolveUser: %v", err) } if !u.ExternalAuth { t.Fatal("auto-created user should have ExternalAuth set") } if u.PasswordHash.Valid { t.Fatal("auto-created external user should have no password hash") } } func TestExtAuthResolveUserNoAutoCreate(t *testing.T) { db := openTestDB(t) e := &ExtAuth{DB: db, Config: config.ExtAuthConfig{AutoCreateUsers: false}} if _, err := e.ResolveUser("ghost"); err == nil { t.Fatal("expected error when auto-create is disabled and user doesn't exist") } }