package authz import ( "crypto/rand" "encoding/base64" "encoding/json" "net/http" "net/http/httptest" "os" "testing" "git.sr.ht/~sircmpwn/core-go/crypto" "github.com/fernet/fernet-go" "github.com/vaughan0/go-ini" ) // testConf holds the crypto keys shared by every test. api-origin is filled in // per-test (it points at an ephemeral httptest server). var testConf ini.File // TestMain synthesizes an in-memory config with a fresh Fernet network-key and // an ed25519 webhook seed, then runs crypto.InitCrypto so Encrypt/Decrypt work. func TestMain(m *testing.M) { var fk fernet.Key if err := fk.Generate(); err != nil { panic("generate fernet key: " + err.Error()) } seed := make([]byte, 32) if _, err := rand.Read(seed); err != nil { panic("generate webhook seed: " + err.Error()) } testConf = ini.File{ "sr.ht": ini.Section{"network-key": fk.Encode()}, "webhooks": ini.Section{"private-key": base64.StdEncoding.EncodeToString(seed)}, "compare.sr.ht": ini.Section{"origin": "http://localhost"}, } crypto.InitCrypto(testConf) os.Exit(m.Run()) } // sealCookie builds a valid unified-login cookie value carrying the given name. func sealCookie(t *testing.T, name string) string { t.Helper() payload, err := json.Marshal(map[string]string{"name": name}) if err != nil { t.Fatalf("marshal claims: %v", err) } return string(crypto.Encrypt(payload)) } func TestUsernameFromRequest_RoundTrip(t *testing.T) { r := httptest.NewRequest(http.MethodGet, "/", nil) r.AddCookie(&http.Cookie{Name: CookieName, Value: sealCookie(t, "bigbes")}) if got := UsernameFromRequest(r); got != "bigbes" { t.Fatalf("username = %q, want %q", got, "bigbes") } } func TestUsernameFromRequest_StripsTilde(t *testing.T) { r := httptest.NewRequest(http.MethodGet, "/", nil) r.AddCookie(&http.Cookie{Name: CookieName, Value: sealCookie(t, "~bigbes")}) if got := UsernameFromRequest(r); got != "bigbes" { t.Fatalf("username = %q, want %q", got, "bigbes") } } func TestUsernameFromRequest_GarbageCookie(t *testing.T) { r := httptest.NewRequest(http.MethodGet, "/", nil) r.AddCookie(&http.Cookie{Name: CookieName, Value: "not-a-valid-fernet-token"}) if got := UsernameFromRequest(r); got != "" { t.Fatalf("username = %q, want empty", got) } } func TestUsernameFromRequest_MissingCookie(t *testing.T) { r := httptest.NewRequest(http.MethodGet, "/", nil) if got := UsernameFromRequest(r); got != "" { t.Fatalf("username = %q, want empty", got) } } func TestUsernameFromRequest_NonJSONPayload(t *testing.T) { r := httptest.NewRequest(http.MethodGet, "/", nil) // A well-formed Fernet token whose plaintext is not JSON. r.AddCookie(&http.Cookie{Name: CookieName, Value: string(crypto.Encrypt([]byte("plain text")))}) if got := UsernameFromRequest(r); got != "" { t.Fatalf("username = %q, want empty", got) } } func TestMiddlewareAndForContext(t *testing.T) { var seen string h := Middleware()(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { seen = ForContext(r.Context()) })) r := httptest.NewRequest(http.MethodGet, "/", nil) r.AddCookie(&http.Cookie{Name: CookieName, Value: sealCookie(t, "bigbes")}) h.ServeHTTP(httptest.NewRecorder(), r) if seen != "bigbes" { t.Fatalf("ForContext = %q, want %q", seen, "bigbes") } // Anonymous request: middleware still runs, ForContext yields "". seen = "sentinel" h.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, "/", nil)) if seen != "" { t.Fatalf("anonymous ForContext = %q, want empty", seen) } } func TestForContext_NoValue(t *testing.T) { if got := ForContext(httptest.NewRequest(http.MethodGet, "/", nil).Context()); got != "" { t.Fatalf("ForContext on bare context = %q, want empty", got) } }