fix: reset-password and the admin password set stamp tokens_valid_after in whole seconds, so a token minted in the same second is accepted

JWT iat is whole seconds and every cutoff check is iat before tokens_valid_after, so a sub-second cutoff refused the login that followed a reset within the same second. POST /reset-password and the admin Users password set now truncate the cutoff to the second, like RevokeUserAccess; email_verified_at keeps full precision. Change-password and bouncer's comparison are unchanged.
This commit is contained in:
Jakub Zych
2026-10-22 22:56:38 +02:00
parent b6f8055c56
commit a484d0e9a2
3 changed files with 123 additions and 4 deletions

View File

@@ -417,11 +417,14 @@ func ResetPassword(app *backpack.App) http.HandlerFunc {
}
// The reset code was mailed to the account's address, so the write
// that stores the new password also records the proof of that
// address (D-27). A reset that fails here records nothing.
// 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.
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,
"tokens_valid_after": now.Truncate(time.Second),
"email_verified_at": now,
}).Error; err != nil {
writeOpaque500(w)