Skip to content

feat: recovery sessions and account notices for the v0.20 auth server - #233

Merged
Bccorb merged 1 commit into
mainfrom
feat/v020-recovery-and-notices
Oct 25, 2026
Merged

Bccorb merged 1 commit into
mainfrom
feat/v020-recovery-and-notices

Conversation

@Bccorb

@Bccorb Bccorb commented Oct 25, 2026

Copy link
Copy Markdown
Contributor

Part of #232: the v0.20 contract pieces. The hardening issues #225 to #231 are in a separate PR. Updates @seamless-auth/types to 0.31.0 (core, nextjs) and syncs the bundled manifest from the API's adapter-manifest.json.

Recovery sessions

  • Core authenticateCookie / authenticateBearer / authenticateRequest take allowRecoverySession. Without it, an access token carrying recovery: true is refused with 403 { error: "enrollment_required" }. For cookies the claim is read from the API token inside the cookie; for bearer, from the verified claims.
  • Express and Fastify requireAuth({ allowRecoverySession }). Next.js getSeamlessSession / getSeamlessClaims treat a recovery session as signed out unless the option is set. hasSeamlessSession still reports it, since the session can use the auth routes to enroll.
  • Dropping the claim after enrollment is left to the client: the next POST /auth/refresh returns a token without it once the API has lifted the restriction. The React SDK will refresh after enrollment (react#185).
    • Doing it here would mean hooking each framework's passkey and TOTP enrollment paths, and ensureCookies, which the hardening PR touches.
    • The Rust adapter does it in cookie transport. We can follow up here if we want parity.

Account notices (fells-code/seamless-auth-api#500)

  • deliverAuthMessage handles account_notice_email / account_notice_sms.
    • The default sends the API's subject and text as plain text, with no HTML template, so nothing is interpolated into markup.
    • New handlers sendAccountNoticeEmail / sendAccountNoticeSms and overrides accountNoticeEmail / accountNoticeSms.
  • deliverableNoticeKinds(messaging) lists only the kinds the configuration can really send (email transport or notice handler; same for SMS).
  • handleManifestRoute on a notices: true route, when at least one kind is deliverable:
    • sends external delivery with the delivery service token plus x-seamless-auth-delivery-kinds
    • delivers every item in deliveries, then strips the field
    • fails closed when none is delivered: the API already counted them as the owner being told, so it POSTs /recovery/cancel with the token from the cancelUrl fragment and answers 503 recovery_notice_failed. Failures log the error name only, never the link.
    • With no deliverable kind the request goes out unchanged and the API sends the notices itself.
  • An OTP with purpose: "account_recovery" is worded as a recovery code, and purpose is passed to custom OTP handlers and overrides.
  • Manifest route type and parseAdapterManifest accept the optional notices: true.

Routes

The new API routes (/recovery/*, /recovery-codes*, /recovery/requests*, /system-config/policy*) are served by the existing manifest catch-all, as AGENTS.md describes. They now appear in the bundled fallback manifest too. 403 step_up_required, 409 secret_required and 409 policy_version_conflict pass through as before via readPassthroughFailure.

Checks

  • pnpm build: clean.
  • pnpm test: core 442, express 218, fastify 182, nextjs 180, all passing.
    • New tests: guard refusal and opt-in (core, express, fastify, nextjs), notice delivery and wording, deliverableNoticeKinds, and the notice-route proxy (headers, delivery, one of two sent keeps the request, none sent cancels and answers 503).
  • pnpm check:types-current: current.

Changeset: minor for core, express, fastify and nextjs, because refusing recovery sessions by default is a behaviour change. Under 0.x a minor marks it.

Guards refuse an access token carrying recovery: true with 403 enrollment_required unless
allowRecoverySession is set (Express and Fastify requireAuth, Next.js session helpers).

On manifest routes marked notices: true, an adapter with messaging names the notice kinds it
can send in x-seamless-auth-delivery-kinds and delivers the returned account_notice_email /
account_notice_sms items. If none goes out it cancels the recovery request with the token in
the cancel link and answers 503 recovery_notice_failed. A recovery OTP is worded as one.

Updates @seamless-auth/types to 0.31.0 and the bundled manifest.

Refs #232
@Bccorb
Bccorb merged commit 5ebc6a2 into main Oct 25, 2026
4 checks passed
@Bccorb
Bccorb deleted the feat/v020-recovery-and-notices branch October 25, 2026 14:54
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.

1 participant