auth: clear session cookie under both host-only and parent-domain scope

Logout emitted a single Set-Cookie scoped to the configured cookie domain
(parodia.dev). A browser holding the session under the older host-only scope
(news.parodia.dev, from before the cookie domain widened for the games site)
was never cleared, so logout looked like a no-op and stranded the user on a
stale session logout couldn't reach. Clear both scopes.

Also surface the who-page owner unlock's previously-silent misses: a genuine
lookup/decode error, and a signed-in session that carries no username. Both
used to fail with err discarded and no log, making a stuck owner undiagnosable.
This commit is contained in:
prosolis
2026-07-17 18:57:01 -07:00
parent 9d9cfd9f9a
commit 1159e64505
3 changed files with 77 additions and 4 deletions
+50
View File
@@ -8,6 +8,56 @@ import (
"golang.org/x/oauth2"
)
// TestClearCookieClearsBothScopes: when a parent cookie domain is configured,
// clearCookie must emit a delete for BOTH the parent-domain scope and the
// host-only scope. A browser holding a session under the older host-only scope
// (from before the cookie domain widened for the games site) would otherwise
// survive logout and keep the user signed in with a session logout can't reach.
func TestClearCookieClearsBothScopes(t *testing.T) {
a := &Authenticator{domain: "parodia.dev"}
rec := httptest.NewRecorder()
a.clearCookie(rec, sessionCookie)
var hostOnly, scoped bool
for _, c := range rec.Result().Cookies() {
if c.Name != sessionCookie {
continue
}
if c.MaxAge >= 0 {
t.Errorf("clear cookie should expire the session, got MaxAge=%d", c.MaxAge)
}
switch c.Domain {
case "":
hostOnly = true
case "parodia.dev":
scoped = true
default:
t.Errorf("unexpected clear Domain %q", c.Domain)
}
}
if !hostOnly {
t.Error("missing host-only clear (no Domain) — stale host-only sessions stay stranded")
}
if !scoped {
t.Error("missing parent-domain clear (Domain=parodia.dev)")
}
}
// With no cookie domain configured, only the host-only clear is emitted.
func TestClearCookieHostOnlyWhenNoDomain(t *testing.T) {
a := &Authenticator{}
rec := httptest.NewRecorder()
a.clearCookie(rec, sessionCookie)
got := rec.Result().Cookies()
if len(got) != 1 {
t.Fatalf("want exactly one clear cookie, got %d", len(got))
}
if got[0].Domain != "" {
t.Errorf("want host-only clear, got Domain=%q", got[0].Domain)
}
}
func TestSignVerifyRoundTrip(t *testing.T) {
a := &Authenticator{secret: []byte("test-secret-key-at-least-16")}
payload := []byte(`{"sub":"abc","exp":123}`)