fix(auth): logout actually signs out — clear the HttpOnly session cookie
Release / build-and-push (push) Successful in 3m9s

Logout wiped localStorage but never cleared the token cookie (HttpOnly —
only a server response can) and never called the logout API, leaving the
refresh token live for 30 days and every cookie-authenticated SSR page
accessible: the fake-signout bug. Reproduced on prod and dev; unrelated
to RLS (it sits behind authentication) and long-standing.

- /api/auth/logout now ALWAYS clears the session cookie (Set-Cookie
  empty + Max-Age=-1, HttpOnly/Path/SameSite mirroring login) — even a
  malformed body must sign the browser out; refresh-token revocation
  unchanged.
- The header logout button (and the rules-page copy) now POST to the
  API with the stored refresh token BEFORE clearing local state and
  redirecting — best effort: local cleanup never blocks on API failure.
- toast 401 handler fixed: it cleared 'refreshToken' but storage uses
  'refresh_token' — the real key.
- Regression test TestLogoutClearsSession: real login → logout →
  response deletes the cookie; logged-out refresh token can't mint
  access tokens; body-less logout still clears.

Also carries regenerated artifacts from the confirm-modal and
episode-list work (header_templ/podcast_detail_templ/style.css).
This commit is contained in:
John O'Keefe committed 2026-10-29 22:01:32 -04:00
1 parent 253d8d0e8e
commit 7ea84043fa
8 files changed
+653 -228

No files matched your search

+17
View File
@@ -74,6 +74,23 @@ func (h *AuthHandler) RefreshAccessToken(c *echo.Context) error {
// Logout handles POST /api/auth/logout
func (h *AuthHandler) Logout(c *echo.Context) error {
// Clear the HttpOnly session cookie — the SSR pages authenticate
// with it, and JavaScript cannot clear an HttpOnly cookie by
// design. Without this, "logout" wiped localStorage but left the
// cookie session alive for up to session_duration_seconds (the
// fake-signout bug: users roamed every cookie-authed page). The
// cookie is cleared BEFORE anything else — even a malformed body
// must sign the browser out.
c.SetCookie(&http.Cookie{
Name: "token",
Value: "",
Path: "/",
HttpOnly: true,
Secure: secure == "true",
SameSite: http.SameSiteLaxMode,
MaxAge: -1, // delete
})
var req RefreshTokenRequest
if err := c.Bind(&req); err != nil {
return c.JSON(http.StatusOK, map[string]string{"message": "logged out successfully"})