package authz
import (
"crypto/rand"
"encoding/base64"
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"testing"
"sourcecraft.dev/bigbes/sr-ht-core/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)
}
}