fix: a password change or ban ends authorized device sessions

The device hand-over now refuses a session authorized before the user's
tokens_valid_after, and every AccessRevokedEvent revokes the user's
pending and confirmed device sessions inside the write's transaction
(classes.RevokeDeviceSessions).
This commit is contained in:
2026-10-26 16:08:20 +01:00
parent 20e7b175f8
commit 557c03c595
5 changed files with 132 additions and 6 deletions

View File

@@ -116,6 +116,7 @@ func (p *Plugin) Boot(app *backpack.App) error {
if err := p.bootBackendLinks(app); err != nil {
return err
}
p.bootDeviceRevocation(app)
if err := p.bootAPITokenScopes(app); err != nil {
return err
}
@@ -125,6 +126,31 @@ func (p *Plugin) Boot(app *backpack.App) error {
return classes.RegisterUserAuthorizer(app)
}
// bootDeviceRevocation revokes the pending and confirmed device sessions of
// users who lose access (a ban or a password change, AccessRevokedEvent)
// inside the write's transaction, so a device authorized before the change
// is never handed a session token after it; a failed write rolls the change
// back.
func (p *Plugin) bootDeviceRevocation(app *backpack.App) {
if app == nil || app.Events == nil {
return
}
app.Events.Listen(p.ID(), func(ctx context.Context, ev *classes.AccessRevokedEvent) error {
if ev == nil || len(ev.UserIDs) == 0 {
return nil
}
db := ev.Tx
if db == nil {
gdb, ok := app.Lookup[*gorm.DB]()
if !ok || gdb == nil {
return fmt.Errorf("golem15.user: revoke device sessions: database is not configured")
}
db = gdb
}
return classes.RevokeDeviceSessions(ctx, db, ev.UserIDs...)
})
}
// bootAPITokenScopes compiles the member token scope catalog from the
// activated plugins (pact.HasFrontendTokenScopes), checks that every phrase
// key it names exists, and publishes it. A bad declaration fails boot.