fix: Displace prior password-reset tokens when minting a new one #76

Merged
rosa merged 1 commit from fix/single-valid-reset-token into main 2026-07-03 21:34:21 +00:00
Owner

Fixes #59.

Problem

run_password_reset inserts a password-reset confirmation, then enqueues the reset email as a separate fallible call. The apalis handler maps every failure to a retryable error, and prior reset tokens were never removed — so a transient mailer-enqueue failure after the insert re-ran the flow and minted an additional simultaneously-valid 30-minute token per retry.

Fix

create_password_reset_confirmation now atomically displaces prior password_reset confirmations for the same user: the Postgres adapter deletes them and inserts the new row in one transaction, and the in-memory mock mirrors the semantics. At most one reset link is valid per user at any time, and retried jobs converge on a single valid token.

Untouched, per the brief: email-confirmation rows for the same user, other users' reset tokens, the unknown-email quiet-success path, the apalis retry policy, and the 30-minute TTL.

Tests

  • New conformance suite crates/infra/tests/confirmation_repository_conformance.rs (Postgres + in-memory): minting displaces the prior reset but not the email confirmation, and displacement is scoped to the requesting user.
  • Domain service tests: running run_password_reset twice leaves the older token failing verification and the newer one succeeding; a pending email-confirmation survives a reset request.
  • mise run ci passes.
Fixes #59. ## Problem `run_password_reset` inserts a password-reset confirmation, then enqueues the reset email as a separate fallible call. The apalis handler maps every failure to a retryable error, and prior reset tokens were never removed — so a transient mailer-enqueue failure after the insert re-ran the flow and minted an additional simultaneously-valid 30-minute token per retry. ## Fix `create_password_reset_confirmation` now atomically displaces prior `password_reset` confirmations for the same user: the Postgres adapter deletes them and inserts the new row in one transaction, and the in-memory mock mirrors the semantics. At most one reset link is valid per user at any time, and retried jobs converge on a single valid token. Untouched, per the brief: email-confirmation rows for the same user, other users' reset tokens, the unknown-email quiet-success path, the apalis retry policy, and the 30-minute TTL. ## Tests - New conformance suite `crates/infra/tests/confirmation_repository_conformance.rs` (Postgres + in-memory): minting displaces the prior reset but not the email confirmation, and displacement is scoped to the requesting user. - Domain service tests: running `run_password_reset` twice leaves the older token failing verification and the newer one succeeding; a pending email-confirmation survives a reset request. - `mise run ci` passes.
fix: Displace prior password-reset tokens when minting a new one
Some checks failed
ci/woodpecker/pr/test Pipeline is pending
ci/woodpecker/push/test Pipeline was canceled
239ff09a9c
A transient mailer-enqueue failure after the confirmation insert made the
apalis job retry the whole reset flow, minting an additional
simultaneously-valid 30-minute token on every retry.

Make create_password_reset_confirmation atomically delete the user's prior
password_reset confirmations in the same transaction as the insert, so at
most one reset link is valid per user and retries converge on a single
token. Email confirmations for the same user, other users' reset tokens,
and the unknown-email quiet-success path are untouched. The in-memory mock
repository mirrors the displacement semantics.

Fixes #59
rosa merged commit 54a337ecaf into main 2026-07-03 21:34:21 +00:00
rosa deleted branch fix/single-valid-reset-token 2026-07-03 21:34:21 +00:00
Sign in to join this conversation.
No reviewers
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
rosa/vernier!76
No description provided.