fix: logout revokes the session under serve: the jwt blacklist and GORM hooks are wired when the database is published

Boot runs before summer serve publishes the database, so the plugin's GORM
hooks, the Postgres jwt_blacklist store and its sweep now go through
lagoon.OnDatabase, and the jwt guard resolves the published store on every
request. A logged-out token is refused on jwt.auth, /fetch and /refresh.

The jwt guard reads the cookie names from classes.SessionCookieNames().
This commit is contained in:
Jakub Zych
2026-10-22 22:29:59 +02:00
parent 8e4b863a3f
commit 4bf44fc6ea
6 changed files with 294 additions and 22 deletions

61
blacklist.go Normal file
View File

@@ -0,0 +1,61 @@
package user
import (
"context"
"time"
"git.golem15.com/golem15/summercms/modules/backpack"
"git.golem15.com/golem15/summercms/modules/bouncer"
)
var _ bouncer.BlacklistStore = (*appBlacklist)(nil)
// appBlacklist is the blacklist store the jwt guard is built with. The guard
// is built in Boot, which under summer serve runs before the database is
// published, so the store the plugin publishes on lagoon.Publish is resolved
// on every call. While none is published, no token counts as blacklisted and
// Add and Sweep do nothing. An appBlacklist must never be published as the
// app's bouncer.BlacklistStore: the lookup would then find itself.
type appBlacklist struct {
app *backpack.App
}
// store returns the published blacklist store, or nil when there is none
// (or the published value is an appBlacklist).
func (b *appBlacklist) store() bouncer.BlacklistStore {
if b == nil || b.app == nil {
return nil
}
s, ok := b.app.Lookup[bouncer.BlacklistStore]()
if !ok || s == nil {
return nil
}
if _, self := s.(*appBlacklist); self {
return nil
}
return s
}
func (b *appBlacklist) Add(ctx context.Context, jti string, expiresAt, validUntil time.Time) error {
s := b.store()
if s == nil {
return nil
}
return s.Add(ctx, jti, expiresAt, validUntil)
}
func (b *appBlacklist) IsBlacklisted(ctx context.Context, jti string) (bool, error) {
s := b.store()
if s == nil {
return false, nil
}
return s.IsBlacklisted(ctx, jti)
}
func (b *appBlacklist) Sweep(ctx context.Context, now time.Time) error {
s := b.store()
if s == nil {
return nil
}
return s.Sweep(ctx, now)
}