fix: access-revoked event on ban and password changes, banned users lose their link, token-specific MCP revoke
- AccessRevokedEvent and FireAccessRevoked: fired in the write's transaction
by the ban action (newly banned users only), the password reset, an
administrator setting a password and the self-service password change;
a revoked subject event per user after commit
- LinkedBackendUser answers no administrator for a banned user
- FireSubjectEvent / FireSubjectEventAfterCommit carry TokenID or ClientID;
DELETE /tokens/{id} ends only that token's MCP sessions
- the MCP personal-token path stamps last_used_ip from towel.ClientIP
- BackendLinksFor replaces the duplicate lookups; requestUsable uses
IsUsableAt
This commit is contained in:
@@ -244,8 +244,15 @@ func (c usersAdminController) FormBeforeUpdate(ctx context.Context, model any) e
|
||||
// user's own change-password does. The cutoff is truncated to whole
|
||||
// seconds, as in RevokeUserAccess, because JWT iat has whole-second
|
||||
// precision, so the user's next login in that second is not refused.
|
||||
now := time.Now().Truncate(time.Second)
|
||||
// OAuth grants created before the untruncated instant are revoked in
|
||||
// the save's transaction (the access-revoked event).
|
||||
at := time.Now()
|
||||
now := at.Truncate(time.Second)
|
||||
user.TokensValidAfter = &now
|
||||
tx, _ := cabana.TxFromContext(ctx)
|
||||
if err := classes.FireAccessRevoked(ctx, c.application(), tx, []uint{user.ID}, at, classes.AccessRevokedPasswordSetByAdmin); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -631,11 +638,50 @@ func (c usersAdminController) AdminBulkActions() []pact.AdminBulkAction {
|
||||
bulk("activate", classes.ActivateUsers),
|
||||
bulk("deactivate", c.deactivateUsers),
|
||||
bulk("restore", classes.RestoreUsers),
|
||||
bulk("ban", classes.BanUsers),
|
||||
bulk("ban", c.banUsers),
|
||||
bulk("unban", classes.UnbanUsers),
|
||||
}
|
||||
}
|
||||
|
||||
// banUsers runs classes.BanUsers unchanged in the action's transaction and
|
||||
// fires the access-revoked event there for the users it newly banned (banned
|
||||
// after, not before), so their OAuth grants are revoked with the ban and
|
||||
// their MCP sessions end once it commits. A listener error rolls the action
|
||||
// back.
|
||||
func (c usersAdminController) banUsers(ctx context.Context, tx *gorm.DB, users []*models.User) (int, error) {
|
||||
ids := make([]uint, 0, len(users))
|
||||
for _, user := range users {
|
||||
if user != nil && user.ID != 0 {
|
||||
ids = append(ids, user.ID)
|
||||
}
|
||||
}
|
||||
before, _, err := classes.ThrottleStates(ctx, tx, ids)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
at := time.Now()
|
||||
changed, err := classes.BanUsers(ctx, tx, users)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
after, _, err := classes.ThrottleStates(ctx, tx, ids)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
newly := make([]uint, 0, len(ids))
|
||||
for _, id := range ids {
|
||||
if after[id] && !before[id] {
|
||||
newly = append(newly, id)
|
||||
}
|
||||
}
|
||||
if len(newly) > 0 {
|
||||
if err := classes.FireAccessRevoked(ctx, c.application(), tx, newly, at, classes.AccessRevokedBan); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return changed, nil
|
||||
}
|
||||
|
||||
// deactivateUsers runs classes.DeactivateUsers unchanged and then fires
|
||||
// user.Deactivating, in the action's transaction, for every selected user
|
||||
// that was not deactivated before. A listener error rolls the action back.
|
||||
|
||||
Reference in New Issue
Block a user