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

@@ -241,7 +241,10 @@ func writeDeviceStatus(w http.ResponseWriter, r *http.Request, gdb *gorm.DB, row
// handOverDevice gives the confirmed session's user a session token for the
// device (D-12a). It reports false, having written nothing, when another
// poll already took the session or it expired meanwhile; the token it
// minted is then dropped unsent.
// minted is then dropped unsent. A user who is gone, deactivated or
// banned, or whose tokens_valid_after (a password change, a revocation)
// is later than the authorization, gets nothing: the session becomes
// revoked, as a token minted at the authorization would be refused.
func handOverDevice(w http.ResponseWriter, r *http.Request, app *backpack.App, gdb *gorm.DB, secret string, row models.DeviceAuthSession) bool {
ctx := r.Context()
var user models.User
@@ -257,7 +260,7 @@ func handOverDevice(w http.ResponseWriter, r *http.Request, app *backpack.App, g
writeOpaque500(w)
return true
}
refused = banned[user.ID]
refused = banned[user.ID] || authorizedBeforeCutoff(&user, row)
}
if refused {
// A deactivated, deleted or banned user signs no device in.
@@ -307,6 +310,16 @@ func handOverDevice(w http.ResponseWriter, r *http.Request, app *backpack.App, g
return true
}
// authorizedBeforeCutoff reports whether row was confirmed before the
// user's tokens_valid_after, compared as the jwt guard compares a token's
// whole-second iat.
func authorizedBeforeCutoff(user *models.User, row models.DeviceAuthSession) bool {
if user.TokensValidAfter == nil || row.ConfirmedAt == nil {
return false
}
return row.ConfirmedAt.Truncate(time.Second).Before(*user.TokensValidAfter)
}
// DeviceAuthorize is POST /devices/authorize: a signed-in user (a valid,
// not blacklisted session token; no second-factor check, D-12c) binds a
// pending device session to themselves by its short_code (hyphens and