package web import ( "net/http" "net/http/httptest" "net/url" "strings" "testing" ) // R-136: the operator session cookie is `__Host-hub_session` — Secure, Path=/, no Domain — even when the // login itself arrived over plain HTTP (the browser would reject a non-Secure __Host- cookie; a sibling // subdomain can never set one). The old `hub_session` name no longer opens a session (the one-time // logout), and plain-HTTP Basic auth for scripts keeps working. // RED-PROOF: set SessionCookieName back to "hub_session" → the name/Secure assertions fail. func TestR136_LoginSetsHostPrefixedCookie(t *testing.T) { s, _ := serverWithPassword(t, "op-pass") h := s.RequireAuth(http.HandlerFunc(s.ServeHTTP)) r := httptest.NewRequest(http.MethodPost, "http://hub.local/login", strings.NewReader(url.Values{"password": {"op-pass"}}.Encode())) r.Header.Set("Content-Type", "application/x-www-form-urlencoded") w := httptest.NewRecorder() h.ServeHTTP(w, r) if w.Code != http.StatusSeeOther { t.Fatalf("login = %d", w.Code) } var sess *http.Cookie for _, c := range w.Result().Cookies() { if c.Name == SessionCookieName { sess = c } } if SessionCookieName != "__Host-hub_session" || sess == nil { t.Fatalf("login set no %q cookie (const=%q, got %v)", "__Host-hub_session", SessionCookieName, w.Result().Cookies()) } raw := w.Header().Get("Set-Cookie") if !sess.Secure || sess.Path != "/" || sess.Domain != "" || strings.Contains(strings.ToLower(raw), "domain=") || !sess.HttpOnly { t.Fatalf("__Host- preconditions broken (plain-HTTP login): %q", raw) } // The new cookie opens the session. r = httptest.NewRequest(http.MethodGet, "/", nil) r.AddCookie(&http.Cookie{Name: SessionCookieName, Value: sess.Value}) w = httptest.NewRecorder() h.ServeHTTP(w, r) if w.Code == http.StatusFound && w.Header().Get("Location") == "/login" { t.Fatal("the __Host- session cookie did not authenticate") } // The same token under the OLD name (a tossed or stale cookie) does not. r = httptest.NewRequest(http.MethodGet, "/", nil) r.AddCookie(&http.Cookie{Name: "hub_session", Value: sess.Value}) w = httptest.NewRecorder() h.ServeHTTP(w, r) if w.Code != http.StatusFound || w.Header().Get("Location") != "/login" { t.Fatalf("old hub_session cookie = %d %q, want a redirect to /login", w.Code, w.Header().Get("Location")) } // Plain-HTTP Basic auth (scripts, no cookie) still reads pages and, with the CLI header, writes. r = httptest.NewRequest(http.MethodGet, "http://hub.local/", nil) r.SetBasicAuth("", "op-pass") w = httptest.NewRecorder() h.ServeHTTP(w, r) if w.Code == http.StatusFound || w.Code == http.StatusUnauthorized { t.Fatalf("plain-HTTP Basic auth GET = %d, want through", w.Code) } if !s.validateCSRF(func() *http.Request { r := httptest.NewRequest(http.MethodPost, "http://hub.local/configuration", nil) r.SetBasicAuth("", "op-pass") r.Header.Set(OperatorCLIHeader, "cli") return r }()) { t.Fatal("plain-HTTP Basic auth + CLI header no longer passes the write gate") } }