package api import ( "net/http" "net/http/httptest" "testing" "time" ) // TestDocsCookieScopedToDocsPath guards the security property that the docs // cookie can never authenticate an /api/v1 request: it must be HttpOnly and // path-scoped to /api/docs. func TestDocsCookieScopedToDocsPath(t *testing.T) { rec := httptest.NewRecorder() req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", nil) req.Header.Set("X-Forwarded-Proto", "https") SetDocsSessionCookie(rec, req, "tok", time.Now().Add(time.Hour)) cookies := rec.Result().Cookies() if len(cookies) != 1 { t.Fatalf("expected 1 cookie, got %d", len(cookies)) } c := cookies[0] if c.Name != DocsCookieName || c.Value != "tok" { t.Errorf("cookie = %s=%s", c.Name, c.Value) } if c.Path != DocsCookiePath { t.Errorf("path = %q, want %q", c.Path, DocsCookiePath) } if !c.HttpOnly || !c.Secure || c.SameSite != http.SameSiteLaxMode { t.Errorf("cookie flags: httpOnly=%v secure=%v sameSite=%v", c.HttpOnly, c.Secure, c.SameSite) } // Clearing expires it on the same path. rec = httptest.NewRecorder() ClearDocsSessionCookie(rec, req) c = rec.Result().Cookies()[0] if c.MaxAge != -1 || c.Path != DocsCookiePath { t.Errorf("clear cookie: maxAge=%d path=%q", c.MaxAge, c.Path) } } // TestDocsPageAuthRedirectsAnonymous: an unauthenticated browser hitting the // docs page is sent to the login page with a return target, not a JSON 401. func TestDocsPageAuthRedirectsAnonymous(t *testing.T) { h := DocsPageAuthMiddleware(nil)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { t.Error("handler should not run for anonymous request") })) rec := httptest.NewRecorder() h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/api/docs?method=post", nil)) if rec.Code != http.StatusFound { t.Fatalf("status = %d, want 302", rec.Code) } if loc := rec.Header().Get("Location"); loc != "/login?redirect=%2Fapi%2Fdocs%3Fmethod%3Dpost" { t.Errorf("Location = %q", loc) } }