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:
@@ -447,13 +447,21 @@ func ResetPassword(app *backpack.App) http.HandlerFunc {
|
||||
// address (D-27). A reset that fails here records nothing. The
|
||||
// session cutoff is truncated to whole seconds, as in
|
||||
// RevokeUserAccess, because JWT iat has whole-second precision, so
|
||||
// the login that follows the reset is not refused.
|
||||
// the login that follows the reset is not refused. The same
|
||||
// transaction fires the access-revoked event with the untruncated
|
||||
// instant, so OAuth grants created before the reset are revoked
|
||||
// with the password change or not at all.
|
||||
now := time.Now()
|
||||
if err := gdb.WithContext(r.Context()).Model(&models.User{}).Where("id = ?", user.ID).Updates(map[string]any{
|
||||
"password": hashed,
|
||||
"tokens_valid_after": now.Truncate(time.Second),
|
||||
"email_verified_at": now,
|
||||
}).Error; err != nil {
|
||||
if err := lagoon.Transaction(r.Context(), gdb, func(ctx context.Context, tx *gorm.DB) error {
|
||||
if err := tx.WithContext(ctx).Model(&models.User{}).Where("id = ?", user.ID).Updates(map[string]any{
|
||||
"password": hashed,
|
||||
"tokens_valid_after": now.Truncate(time.Second),
|
||||
"email_verified_at": now,
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return classes.FireAccessRevoked(ctx, app, tx, []uint{user.ID}, now, classes.AccessRevokedPasswordReset)
|
||||
}); err != nil {
|
||||
writeOpaque500(w)
|
||||
return
|
||||
}
|
||||
@@ -707,13 +715,22 @@ func ChangePassword(app *backpack.App) http.HandlerFunc {
|
||||
writeOpaque500(w)
|
||||
return
|
||||
}
|
||||
// The cutoff keeps the current session (issued at iat) and every
|
||||
// token issued since its sign-in second; the access-revoked event
|
||||
// revokes OAuth grants created before that second, in the same
|
||||
// transaction.
|
||||
cutoff := iat.Add(-time.Second)
|
||||
if err := gdb.WithContext(r.Context()).Model(&user).Updates(map[string]any{
|
||||
"password": hashed,
|
||||
"must_change_password": false,
|
||||
"has_self_set_password": true,
|
||||
"tokens_valid_after": cutoff,
|
||||
}).Error; err != nil {
|
||||
if err := lagoon.Transaction(r.Context(), gdb, func(ctx context.Context, tx *gorm.DB) error {
|
||||
if err := tx.WithContext(ctx).Model(&user).Updates(map[string]any{
|
||||
"password": hashed,
|
||||
"must_change_password": false,
|
||||
"has_self_set_password": true,
|
||||
"tokens_valid_after": cutoff,
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return classes.FireAccessRevoked(ctx, app, tx, []uint{user.ID}, cutoff.Add(time.Second), classes.AccessRevokedPasswordChanged)
|
||||
}); err != nil {
|
||||
writeOpaque500(w)
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user