fix(09): WR-14 let logout revoke an expired token that is still refreshable and always clear the cookie
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -249,10 +250,17 @@ func (s *service) refresh(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// logout blacklists the presented token's jti and always expires the admin
|
||||
// cookie, so a browser session ends even when only the Bearer was revoked.
|
||||
//
|
||||
// Logout is mounted outside the backend guard, which rejects an expired access
|
||||
// token before any handler runs. The token is verified here instead, with
|
||||
// exp unchecked, so a token whose access lifetime has passed but whose refresh
|
||||
// window is still open (and which /auth/refresh would still accept) can be
|
||||
// revoked. The cookie is expired on every outcome, including a refusal.
|
||||
func (s *service) logout(w http.ResponseWriter, r *http.Request) {
|
||||
s.expireSessionCookie(w)
|
||||
raw, _ := sessionToken(r)
|
||||
_, iat, exp, jti, err := bouncer.VerifyClaimsAudience(raw, s.secret, bouncer.AudienceBackend)
|
||||
if err != nil || jti == "" {
|
||||
sub, iat, exp, jti, err := bouncer.VerifyRefreshableClaimsAudience(raw, s.secret, bouncer.AudienceBackend, s.refreshTTL)
|
||||
if err != nil {
|
||||
WriteError(w, http.StatusUnauthorized, "unauthenticated", msgUnauthenticated)
|
||||
return
|
||||
}
|
||||
@@ -267,11 +275,10 @@ func (s *service) logout(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
id := uint(0)
|
||||
if principal, ok := bouncer.User(r.Context()); ok {
|
||||
id = principal.ID
|
||||
if n, err := strconv.ParseUint(sub, 10, 64); err == nil {
|
||||
id = uint(n)
|
||||
}
|
||||
s.logAuth(r, "success", id)
|
||||
s.expireSessionCookie(w)
|
||||
WriteData(w, http.StatusOK, AdminLogoutData{Status: "logged_out"}, map[string]any{})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user