Skip to content

feat(recovery): deliver recovery notices through the adapter, and cancel from a session - #500

Merged
Bccorb merged 1 commit into
mainfrom
feat/recovery-notice-delivery
Oct 25, 2026
Merged

Bccorb merged 1 commit into
mainfrom
feat/recovery-notice-delivery

Conversation

@Bccorb

@Bccorb Bccorb commented Oct 25, 2026

Copy link
Copy Markdown
Contributor

Closes #499. Follow-up to #497 / #498. Updates @seamless-auth/types to 0.31.0.

Recovery notices through the adapter

#498 made /recovery/verify fail closed when no owner notice goes out. Deployments whose adapter owns delivery therefore could not use recovery. Now:

  • sendAccountNotice can return a notice as a delivery payload instead of sending it: account_notice_email / account_notice_sms from types 0.31, with notice: 'account_recovery_opened' and the cancelUrl. A returned notice counts as notifying the owner (returned_for_external_delivery).
  • It only does this for a credentialed external caller that lists the kinds in the new x-seamless-auth-delivery-kinds header. Without the header, external delivery still records not_sent_external_delivery, and verify still answers 503.
  • Why the header: released adapters (express v0.4.0 has a manifest catch-all) already forward /recovery/verify, and their delivery switch logs an unknown kind and drops it. Returning notices to them would bring fix(recovery): do not open an account recovery request unless the owner was notified #497 back.
  • /recovery/verify responds with deliveries: AuthDeliveryInstruction[] (API-local AccountRecoveryVerifyResponseSchema). The adapter manifest marks it notices: true, a new optional field that older adapters' parseAdapterManifest ignores. registerAdapterRoute refuses notices on a route whose response has no deliveries.
  • The cancel link is never written to the audit event; only per-channel statuses are logged.
  • The recovery code from /recovery/start carries purpose: 'account_recovery' under external delivery. Older adapters ignore the field and still deliver the code.

Cancel from a signed-in session

  • GET /recovery/requests (access): the account's open requests { id, channel, state, requestedAt, completesAt }.
  • DELETE /recovery/requests/:id (access): cancels one without the token. It is scoped to the user, so another account's id gets 404, and it logs account_recovery_cancelled with via: 'session'.
  • Neither is gated on the feature being on, matching token cancel. Recovery sessions are refused by default (enrollment_required).
  • Both are plain access routes in the manifest, so the adapter catch-all forwards them with no adapter change.

Contract / ripple

  • New header x-seamless-auth-delivery-kinds, new manifest field notices, new deliveries field on /recovery/verify, and two new access routes.
  • seamless-auth-server#232 and the Go/Python/Rust adapters: handle the two notice kinds, then send external delivery plus x-seamless-auth-delivery-kinds: account_notice_email,account_notice_sms on notices: true routes and deliver every item in deliveries.
  • React: recovery UI can list and cancel open requests.
  • I will add these to the adapter issues.

Checks

  • npm run typecheck, npm run lint, npm run format:check: pass
  • npm run coverage: 2350 passed, all files 98.91 / 95.31 / 99.24 / 99.11
  • tests/postgres/accountRecovery.spec.ts, with a new list/cancel ownership case, run against a scratch Postgres with the full migration chain: 4 passed

…cel from a session

/recovery/verify returns the opened notices as deliveries to a credentialed caller that lists
account_notice_email / account_notice_sms in x-seamless-auth-delivery-kinds; the manifest marks
the route notices: true. Callers that do not list the kinds still get the fail-closed 503, so
an older adapter never receives a kind it would drop. The recovery code carries purpose
account_recovery. GET /recovery/requests and DELETE /recovery/requests/:id let a signed-in
owner see and cancel open requests without the token. Updates @seamless-auth/types to 0.31.0.

Closes #499
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.

feat(recovery): deliver recovery notices through external delivery, and cancel from a signed-in session

1 participant