package authn import ( "net/http" "net/http/httptest" "testing" "gsg/internal/session" ) func TestSafeNext(t *testing.T) { cases := []struct { in, want string }{ {"", "/"}, {"/", "/"}, {"/demo/", "/demo/"}, {"/share/new?x=1", "/share/new?x=1"}, {"//evil.example", "/"}, {"https://evil.example", "/"}, {"http://evil.example/path", "/"}, {"relative", "/"}, } for _, tc := range cases { if got := safeNext(tc.in); got != tc.want { t.Errorf("safeNext(%q) = %q, want %q", tc.in, got, tc.want) } } } func TestLoginRequiresIdentityHeader(t *testing.T) { h := testHandlers(t) req := httptest.NewRequest("GET", "/auth/login", nil) rec := httptest.NewRecorder() h.Login.ServeHTTP(rec, req) if rec.Code != http.StatusUnauthorized { t.Fatalf("status = %d, want %d", rec.Code, http.StatusUnauthorized) } if cookies := rec.Result().Cookies(); len(cookies) != 0 { t.Fatalf("unauthenticated login set cookies: %v", cookies) } } func TestLoginIssuesCookieAndHonorsSafeNext(t *testing.T) { h := testHandlers(t) req := httptest.NewRequest("GET", "/auth/login?next=/demo/", nil) req.Header.Set("X-Authentik-Username", "alice") rec := httptest.NewRecorder() h.Login.ServeHTTP(rec, req) if rec.Code != http.StatusSeeOther { t.Fatalf("status = %d, want %d", rec.Code, http.StatusSeeOther) } if got := rec.Header().Get("Location"); got != "/demo/" { t.Fatalf("Location = %q, want /demo/", got) } cookies := rec.Result().Cookies() if len(cookies) != 1 || cookies[0].Name != session.CookieName { t.Fatalf("cookies = %v, want one %s cookie", cookies, session.CookieName) } } func TestLoginRejectsOffsiteNext(t *testing.T) { h := testHandlers(t) req := httptest.NewRequest("GET", "/auth/login?next=https://evil.example/", nil) req.Header.Set("X-Authentik-Username", "alice") rec := httptest.NewRecorder() h.Login.ServeHTTP(rec, req) if got := rec.Header().Get("Location"); got != "/" { t.Fatalf("Location = %q, want /", got) } } func TestLogoutClearsCookie(t *testing.T) { h := testHandlers(t) req := httptest.NewRequest("GET", "/auth/logout", nil) rec := httptest.NewRecorder() h.Logout.ServeHTTP(rec, req) if rec.Code != http.StatusSeeOther { t.Fatalf("status = %d, want %d", rec.Code, http.StatusSeeOther) } if got := rec.Header().Get("Location"); got != "/" { t.Fatalf("Location = %q, want /", got) } cookies := rec.Result().Cookies() if len(cookies) != 1 || cookies[0].Name != session.CookieName { t.Fatalf("cookies = %v, want one %s cookie", cookies, session.CookieName) } if cookies[0].MaxAge >= 0 { t.Fatalf("logout cookie MaxAge = %d, want negative", cookies[0].MaxAge) } } func testHandlers(t *testing.T) Handlers { t.Helper() h, err := NewHandlers(Config{Sessions: session.NewManager()}) if err != nil { t.Fatalf("NewHandlers: %v", err) } return h }