Files

127 lines
4.0 KiB
Go

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")
}
}