package authn import ( "net/http" "net/http/httptest" "testing" "git.sr.ht/~sircmpwn/core-go/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) } }