package user import ( "net/http" "sync/atomic" "testing" "git.golem15.com/golem15/sm-user-plugin/classes" "git.golem15.com/golem15/summercms/modules/backpack" "git.golem15.com/golem15/summercms/modules/bouncer" "gorm.io/gorm" ) // TestBootBeforeDatabase pins logout revocation when Boot runs before the // database is published, as under summer serve: nothing database-backed is // installed before lagoon.Publish, the Postgres blacklist store and the GORM // hooks are installed when it runs, and a token ended by POST /logout is // refused on jwt.auth through every carrier, at /fetch and at /refresh, with // its jwt_blacklist row. func TestBootBeforeDatabase(t *testing.T) { // The classes hook registrants are process-global and cannot be removed, // so the test hook is gated and goes inert once this test ends. var hooked atomic.Pointer[gorm.DB] var active atomic.Bool active.Store(true) t.Cleanup(func() { active.Store(false) }) classes.RegisterHook(func(gdb *gorm.DB) error { if active.Load() { hooked.Store(gdb) } return nil }) e := newServeOrderEnv(t, func(app *backpack.App) { if _, ok := app.Lookup[*gorm.DB](); ok { t.Fatal("a *gorm.DB is published before lagoon.Publish") } if _, ok := app.Lookup[bouncer.BlacklistStore](); ok { t.Fatal("a bouncer.BlacklistStore is published before lagoon.Publish") } if hooked.Load() != nil { t.Fatal("the GORM hooks ran before lagoon.Publish") } }) store, ok := e.app.Lookup[bouncer.BlacklistStore]() if !ok { t.Fatal("no bouncer.BlacklistStore published after lagoon.Publish") } if _, ok := store.(*bouncer.PostgresBlacklist); !ok { t.Fatalf("published blacklist store is %T, want *bouncer.PostgresBlacklist", store) } if got := hooked.Load(); got != e.gdb { t.Fatalf("the GORM hooks ran with %p, want the published handle %p", got, e.gdb) } token := e.sessionLogin(t, "serve-order") if code := e.guarded(t, token, ""); code != http.StatusOK { t.Fatalf("jwt.auth before logout (bearer) = %d", code) } for _, name := range classes.SessionCookieNames() { if code := e.guarded(t, token, name); code != http.StatusOK { t.Fatalf("jwt.auth before logout (cookie %q) = %d", name, code) } } assertLoggedOut(t, e.call(t, http.MethodPost, userLogoutPath, token, nil)) e.assertSessionEnded(t, token) jti, _ := parseToken(t, token)["jti"].(string) if jti == "" { t.Fatal("session token has no jti") } var rows int64 if err := e.gdb.Raw(`SELECT COUNT(*) FROM jwt_blacklist WHERE jti = ?`, jti).Scan(&rows).Error; err != nil { t.Fatal(err) } if rows != 1 { t.Fatalf("jwt_blacklist rows for %s = %d, want 1", jti, rows) } }