Files
sm-user-plugin/blacklist_test.go
Jakub Zych 4bf44fc6ea 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().
2026-10-22 22:29:59 +02:00

87 lines
2.7 KiB
Go

package user
import (
"errors"
"testing"
"time"
"git.golem15.com/golem15/summercms/modules/bouncer"
)
// TestAppBlacklist: the jwt guard's store resolves the app's published
// bouncer.BlacklistStore on every call. With none published (or only an
// appBlacklist, which would recurse) no token is blacklisted and Add and
// Sweep do nothing; otherwise calls are delegated and a store error comes
// back unchanged, so the guard still fails closed.
func TestAppBlacklist(t *testing.T) {
ctx := t.Context()
now := time.Now()
newApp := func() *appBlacklist {
return &appBlacklist{app: sessionUserApp(t, "SUMMER_GOLEM15__USER__JWT__SECRET="+sessionSecret)}
}
assertNoStore := func(t *testing.T, bl *appBlacklist) {
t.Helper()
if got, err := bl.IsBlacklisted(ctx, "jti-none"); got || err != nil {
t.Fatalf("IsBlacklisted with no store = %v, %v", got, err)
}
if err := bl.Add(ctx, "jti-none", now.Add(time.Hour), now); err != nil {
t.Fatalf("Add with no store = %v", err)
}
if err := bl.Sweep(ctx, now); err != nil {
t.Fatalf("Sweep with no store = %v", err)
}
}
t.Run("no store", func(t *testing.T) {
assertNoStore(t, newApp())
})
t.Run("wrapper published as the store", func(t *testing.T) {
bl := newApp()
if err := bl.app.Publish[bouncer.BlacklistStore](&appBlacklist{app: bl.app}); err != nil {
t.Fatal(err)
}
assertNoStore(t, bl)
})
t.Run("delegates", func(t *testing.T) {
bl := newApp()
mem := bouncer.NewMemoryBlacklist()
if err := bl.app.Publish[bouncer.BlacklistStore](mem); err != nil {
t.Fatal(err)
}
// Blacklisted from now on, kept for an hour.
if err := bl.Add(ctx, "jti-ended", now.Add(time.Hour), now); err != nil {
t.Fatal(err)
}
if got, err := mem.IsBlacklisted(ctx, "jti-ended"); !got || err != nil {
t.Fatalf("memory store after Add through the wrapper = %v, %v", got, err)
}
if got, err := bl.IsBlacklisted(ctx, "jti-ended"); !got || err != nil {
t.Fatalf("IsBlacklisted(jti-ended) = %v, %v", got, err)
}
if got, err := bl.IsBlacklisted(ctx, "jti-other"); got || err != nil {
t.Fatalf("IsBlacklisted(jti-other) = %v, %v", got, err)
}
if err := bl.Sweep(ctx, now); err != nil {
t.Fatal(err)
}
})
t.Run("store error passes through", func(t *testing.T) {
bl := newApp()
if err := bl.app.Publish[bouncer.BlacklistStore](failingBlacklist{}); err != nil {
t.Fatal(err)
}
if _, err := bl.IsBlacklisted(ctx, "jti"); !errors.Is(err, errBlacklistDown) {
t.Fatalf("IsBlacklisted error = %v", err)
}
if err := bl.Add(ctx, "jti", now.Add(time.Hour), now); !errors.Is(err, errBlacklistDown) {
t.Fatalf("Add error = %v", err)
}
if err := bl.Sweep(ctx, now); !errors.Is(err, errBlacklistDown) {
t.Fatalf("Sweep error = %v", err)
}
})
}