Skip to content

feat: recovery codes, self-service account recovery and passkey management - #211

Merged
Bccorb merged 10 commits into
mainfrom
feat/v020-recovery
Oct 26, 2026
Merged

Bccorb merged 10 commits into
mainfrom
feat/v020-recovery

Conversation

@Bccorb

@Bccorb Bccorb commented Oct 25, 2026

Copy link
Copy Markdown
Contributor

Builds the v0.20 "never locked out" features in the client SDK and the React prebuilt UI, against the auth API's docs/account-recovery.md and adapter manifest on main.

Client (@seamless-auth/client)

  • Recovery codes: getRecoveryCodeStatus(), generateRecoveryCodes() (a 403 step_up_required when replacing a set is recognized by isStepUpRequired(); generateRecoveryCodesWithStepUp() runs the step-up and retries), redeemRecoveryCode(code).
  • Account recovery: startAccountRecovery(channel), verifyAccountRecovery(code), getAccountRecoveryStatus(), completeAccountRecovery(), cancelAccountRecovery(cancelToken), listAccountRecoveryRequests(), cancelAccountRecoveryRequest(id).
  • Recovery sessions: redeem and complete set isRecoverySession() and the session state's recoverySession; session actions redeemRecoveryCode and completeAccountRecovery. After a passkey registration or TOTP enrollment from a recovery session the client calls POST /refresh (new Transport.refresh()), so the access token drops the recovery claim.
  • Bearer ROUTE_RULES for every recovery route, mirroring the manifest credentials (preAuth, access, none), with issue on redeem and complete.
  • Shared flows for every binding: loadRecoveryOptions, recoveryCancelTokenFromHash, cancelRecoveryFromLink, recoveryErrorMessage, recoveryCodesText, passkeyKind, passkeyDisplayName, plus getRecoveryErrorCode() and isEnrollmentRequired().
  • Passkeys: list, rename and delete already existed (credentials, updateCredential, deleteCredential on /users/credentials), so nothing new was added there.

React (@seamless-auth/react)

#185 recovery codes

  • /recovery-codes, shown after a passkey is enrolled: generates and shows a first set (or a new one once every code is spent), copy and download, and a "saved them" confirmation before continuing.
  • "Use a recovery code" on sign-in (/recovery-code), which redeems the code and sends the user to enroll a passkey.
  • RecoveryCodeSettings to regenerate from account settings, stepping up with the passkey when the API asks.

#186 passkey management

  • PasskeyManagement: list with name, kind (synced, device, security key) and last used; rename; delete with confirmation (stronger warning for the last one); add another; a nudge towards a second passkey when only one exists.

#208 self-service recovery

  • /recovery/cancel: reads the token from the fragment, drops it from the address bar, posts it, and shows the outcome.
  • /account-recovery from sign-in, offered only when the public config reports accountRecovery: channel, code, the waiting period (completesAt), "check again", then completion. Handles 503 recovery_notice_failed, 403 account_recovery_disabled, a wrong code and a lapsed pre-auth token.
  • A recovery session goes straight to /register-passkey, which cannot be skipped in that state.
  • RecoveryRequests: open requests with cancel in settings. AccountSecurity renders all three settings sections.

Also: @seamless-auth/types bumped to ^0.31.0 (the changeset for that ships with #210), Svelte's session class exposes the new actions, and the client, React and React Native READMEs document it all.

Checks

npm run lint              pass
npm run typecheck         pass (all six packages)
npm run format:check      All matched files use Prettier code style!
npm test                  Jest: 52 suites, 704 tests passed; Vitest (svelte): 42 passed
npm run build             pass
npm run check-npm-build   pass
npm run coverage          All files 94.13 stmts / 87.93 branches / 88.66 funcs / 95.17 lines
npm run check:types-current  @seamless-auth/types "^0.31.0" is current (latest 0.31.0)
npx commitlint --from origin/main  pass

Contract notes

  • @seamless-auth/types 0.31.0 does not publish the GET /recovery/requests list shape, or the recovery error codes. AccountRecoveryRequest is composed from AccountRecoveryStatus plus id and channel, and RecoveryErrorCode is a local union.
  • GET /users/me does not say whether the session is a recovery session, so the client only knows it in memory. After a reload it cannot route a half-recovered user to enrollment or refresh after enrolling (the adapter's silent refresh fixes the claim once the access token expires).

Needs an auth API with account recovery (v0.20) and a server adapter that proxies its manifest routes, including POST /auth/refresh.

Closes #185
Closes #186
Closes #208

Add client methods for the auth API v0.20 recovery routes, the bearer
route rules that mirror their adapter manifest credentials, and the
shared flows the bundled screens of every binding build on.

A session issued by a recovery code or a completed account recovery is
tracked by the client and mirrored into the session state. After a
passkey or TOTP enrollment made from it, the client refreshes the
session so the new access token no longer carries the recovery claim.
Add the sign-in side of account recovery to the prebuilt UI:

- /recovery-code signs in with a recovery code
- /recovery-codes generates and shows a first set after a passkey is
  enrolled, and asks the user to confirm they saved it
- /account-recovery runs self-service recovery: channel, code, the
  waiting period, then completion
- /recovery/cancel takes the token from the notice link's fragment,
  drops it from the address bar and cancels the request

The sign-in screen offers recovery only where the public configuration
reports it, and a recovery session goes straight to passkey enrollment,
which it cannot skip.

Refs #185, #208
Export router-free settings sections built on the public primitives:

- PasskeyManagement lists passkeys with name, kind and last use, renames
  and deletes them, adds another, and asks for a second passkey when
  there is only one
- RecoveryCodeSettings shows the codes left and generates or replaces a
  set, stepping up with the passkey when the API asks for it
- RecoveryRequests lists open account recovery requests with cancel
- AccountSecurity renders all three

Refs #185, #186, #208
@Bccorb
Bccorb merged commit 44854d0 into main Oct 26, 2026
5 checks passed
@Bccorb
Bccorb deleted the feat/v020-recovery branch October 26, 2026 01:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant