Skip to content

fix(recovery): do not open an account recovery request unless the owner was notified - #498

Merged
Bccorb merged 1 commit into
mainfrom
fix/recovery-requires-owner-notice
Oct 25, 2026
Merged

Bccorb merged 1 commit into
mainfrom
fix/recovery-requires-owner-notice

Conversation

@Bccorb

@Bccorb Bccorb commented Oct 25, 2026

Copy link
Copy Markdown
Contributor

Closes #497.

Self-service recovery relies on its delay and the "recovery opened" notice. The notice is the only place the cancel token is delivered. Until now, /recovery/verify opened the request even when every notice was skipped (external delivery) or failed (no direct transport, provider outage). So the delay could run out without the owner ever being told.

Changes

  • Fail closed. verifyAccountRecovery keeps the request only if at least one notice to a verified address was sent. Otherwise voidUnannouncedRecovery voids it (and clears the cancel token hash), logs account_recovery_failed with reason Owner could not be notified and the notice outcomes, and answers 503 { error: 'recovery_notice_failed' }.
  • Supersede after the notice. Opening a request used to cancel the account's other open requests straight away. That now happens in supersedeOtherRecoveries, only after a notice is sent, so a request the owner never heard about cannot cancel theirs.
  • /recovery/verify documents the 503 in its OpenAPI responses (regenerated openapi.json / src/generated/api.ts).
  • docs/account-recovery.md: the flow and Notes now say the opening notice needs the API's direct transport, and what an external-delivery caller gets.

The resume path is unchanged. A request that opened was announced, and resuming keeps its clock and sends no new notice.

Contract note

/recovery/verify gains a 503 with recovery_notice_failed. This is unreleased (v0.20) and no adapter passes /recovery/* through yet, so it goes into the seamless-auth-server#232 work. Deployments that deliver codes through their adapter can still use recovery as long as the API has a working transport for the notice and the adapter does not request external delivery on verify.

The rest of #497 needs a new delivery contract, so it is left for a @seamless-auth/types minor alongside the adapter wave:

  • a delivery kind for the opened notice and its cancel link
  • a distinct kind for the recovery code
  • cancelling from a signed-in session

Checks

  • npm run typecheck, npm run lint, npm run format:check: pass
  • npm run coverage: 2338 passed, all files 98.88 / 95.26 / 99.23 / 99.1
  • tests/postgres/accountRecovery.spec.ts, including the new supersede/void case, run against a scratch Postgres with the full migration chain: 3 passed

…er was notified

The delay only protects an owner who is told about it, and the opening notice is the only
place the cancel token is delivered. A request now opens only once a notice reaches a verified
address; otherwise it is voided and /recovery/verify answers 503 recovery_notice_failed. Other
open requests are cancelled only after the notice is sent, so an unannounced request cannot
cancel the owner's.

Closes #497
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(recovery): do not open an account recovery request unless the owner was notified

1 participant