package authn
import (
"net/http"
"net/http/httptest"
"testing"
"sourcecraft.dev/bigbes/sr-ht-core/auth"
)
// captureHandler records the caller present on the request context when reached.
func captureHandler(dst **auth.AuthContext, reached *bool) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
*reached = true
*dst = CallerFromContext(r.Context())
w.WriteHeader(http.StatusOK)
})
}
func runCookieMiddleware(t *testing.T, cookie *http.Cookie) (*auth.AuthContext, int) {
t.Helper()
var got *auth.AuthContext
var reached bool
h := OptionalCookieMiddleware()(captureHandler(&got, &reached))
req := httptest.NewRequest(http.MethodGet, "/", nil)
if cookie != nil {
req.AddCookie(cookie)
}
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
if !reached {
t.Fatal("middleware must always call next (never rejects)")
}
return got, rec.Code
}
func TestOptionalCookieMiddleware_NoCookie(t *testing.T) {
withStubBackend(t, &stubBackend{users: map[string]auth.AuthContext{}})
got, code := runCookieMiddleware(t, nil)
if got != nil {
t.Fatalf("expected anonymous, got %+v", got)
}
if code != http.StatusOK {
t.Fatalf("expected 200, got %d", code)
}
}
func TestOptionalCookieMiddleware_ValidCookie(t *testing.T) {
withStubBackend(t, &stubBackend{users: map[string]auth.AuthContext{
"bigbes": sampleUser(1, "bigbes", auth.USER_TYPE_USER),
}})
got, code := runCookieMiddleware(t, &http.Cookie{
Name: CookieName,
Value: forgeCookie(t, "bigbes"),
})
if code != http.StatusOK {
t.Fatalf("expected 200, got %d", code)
}
if got == nil {
t.Fatal("expected an authenticated caller")
}
if got.Username != "bigbes" || got.UserID != 1 {
t.Fatalf("wrong caller resolved: %+v", got)
}
if got.AuthMethod != auth.AUTH_COOKIE {
t.Fatalf("AuthMethod = %q, want %q", got.AuthMethod, auth.AUTH_COOKIE)
}
}
func TestOptionalCookieMiddleware_SuspendedStillResolves(t *testing.T) {
withStubBackend(t, &stubBackend{users: map[string]auth.AuthContext{
"susp": sampleUser(2, "susp", auth.USER_TYPE_SUSPENDED),
}})
got, _ := runCookieMiddleware(t, &http.Cookie{
Name: CookieName,
Value: forgeCookie(t, "susp"),
})
if got == nil {
t.Fatal("suspended users must still resolve (reads are allowed)")
}
if !AsCoreCaller(got).Suspended {
t.Fatal("resolved caller must be flagged suspended")
}
}
func TestOptionalCookieMiddleware_GarbageCookie(t *testing.T) {
withStubBackend(t, &stubBackend{users: map[string]auth.AuthContext{}})
got, code := runCookieMiddleware(t, &http.Cookie{
Name: CookieName,
Value: "not-a-valid-fernet-token",
})
if got != nil {
t.Fatalf("garbage cookie must degrade to anonymous, got %+v", got)
}
if code != http.StatusOK {
t.Fatalf("must not reject: expected 200, got %d", code)
}
}
func TestOptionalCookieMiddleware_UnknownUser(t *testing.T) {
withStubBackend(t, &stubBackend{users: map[string]auth.AuthContext{}})
got, code := runCookieMiddleware(t, &http.Cookie{
Name: CookieName,
Value: forgeCookie(t, "ghost"),
})
if got != nil {
t.Fatalf("unknown user must degrade to anonymous, got %+v", got)
}
if code != http.StatusOK {
t.Fatalf("must not reject: expected 200, got %d", code)
}
}
func TestOptionalCookieMiddleware_BackendDownIsAnonymous(t *testing.T) {
withStubBackend(t, &stubBackend{lookupErr: errBackendDown})
got, code := runCookieMiddleware(t, &http.Cookie{
Name: CookieName,
Value: forgeCookie(t, "bigbes"),
})
if got != nil {
t.Fatalf("backend failure must degrade to anonymous, got %+v", got)
}
if code != http.StatusOK {
t.Fatalf("must not 500: expected 200, got %d", code)
}
}