fix(auth): invalidate prior pending reset tokens when a new reset is requested #284

Merged
YousifShkara merged 1 commit from fix/BUNYIP-256-cap-pending-reset-tokens into main 2026-06-30 06:02:16 +02:00
Owner

BUNYIP-256: shrink the live password-reset-token surface to exactly one per user. When request_password_reset issues a fresh token, any still-valid older tokens for the same user are marked used in the same flow.

The audit's framing was an O(N) Argon2 DoS amplifier - that turned out to be wrong against this codebase: the actual lookup is find_password_reset_token_by_hash doing a single indexed SELECT by SHA-256 hash, no Argon2 loop. The remaining defense-in-depth wins still hold and are worth shipping:

  • Smaller attack surface. Multiple outstanding tokens means multiple parallel guesses; one live token caps that.
  • Cleaner UX. A user who clicks "Forgot password?" three times stops getting confused by three reset emails - only the most recent link works.
  • Matches incident-response expectations. If a token leak is suspected the operator simply has the user request a new reset; older copies stop working immediately instead of staying valid until their hour TTL.

The new TokenRepository::revoke_pending_password_reset_tokens is a single UPDATE; failure is logged and the request proceeds (no false-negative blocking the legitimate reset). complete_password_reset continues to work unchanged because it always operates on the most-recent token.

#BUNYIP-256

BUNYIP-256: shrink the live password-reset-token surface to exactly one per user. When `request_password_reset` issues a fresh token, any still-valid older tokens for the same user are marked used in the same flow. The audit's framing was an O(N) Argon2 DoS amplifier - that turned out to be wrong against this codebase: the actual lookup is `find_password_reset_token_by_hash` doing a single indexed SELECT by SHA-256 hash, no Argon2 loop. The remaining defense-in-depth wins still hold and are worth shipping: - Smaller attack surface. Multiple outstanding tokens means multiple parallel guesses; one live token caps that. - Cleaner UX. A user who clicks "Forgot password?" three times stops getting confused by three reset emails - only the most recent link works. - Matches incident-response expectations. If a token leak is suspected the operator simply has the user request a new reset; older copies stop working immediately instead of staying valid until their hour TTL. The new `TokenRepository::revoke_pending_password_reset_tokens` is a single UPDATE; failure is logged and the request proceeds (no false-negative blocking the legitimate reset). `complete_password_reset` continues to work unchanged because it always operates on the most-recent token. #BUNYIP-256
fix(auth): invalidate prior pending reset tokens when a new reset is requested
All checks were successful
E2E / Playwright against deployment (pull_request) Successful in 54s
Check / fmt + clippy + build + tests (pull_request) Successful in 10m59s
Create release / Create release from merged PR (pull_request) Has been skipped
6aa8ff5628
BUNYIP-256: shrink the live password-reset-token surface to exactly one per user. When `request_password_reset` issues a fresh token, any still-valid older tokens for the same user are marked used in the same flow.

The audit's framing was an O(N) Argon2 DoS amplifier - that turned out to be wrong against this codebase: the actual lookup is `find_password_reset_token_by_hash` doing a single indexed SELECT by SHA-256 hash, no Argon2 loop. The remaining defense-in-depth wins still hold and are worth shipping:

- Smaller attack surface. Multiple outstanding tokens means multiple parallel guesses; one live token caps that.
- Cleaner UX. A user who clicks "Forgot password?" three times stops getting confused by three reset emails - only the most recent link works.
- Matches incident-response expectations. If a token leak is suspected the operator simply has the user request a new reset; older copies stop working immediately instead of staying valid until their hour TTL.

The new `TokenRepository::revoke_pending_password_reset_tokens` is a single UPDATE; failure is logged and the request proceeds (no false-negative blocking the legitimate reset). `complete_password_reset` continues to work unchanged because it always operates on the most-recent token.

#BUNYIP-256
YousifShkara deleted branch fix/BUNYIP-256-cap-pending-reset-tokens 2026-06-30 06:02:16 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/bunyip!284
No description provided.