Repository navigation
fix(users): prepare-device-replacement needs step-up and identity proofing #245
Copy link
Copy link
Open
Labels
P2SoonSoonarea:cliSeamless CLISeamless CLIbugSomething isn't workingSomething isn't workingsafeServes the safe promise: no lockout, no lock-in, honestServes the safe promise: no lockout, no lock-in, honestv0.20Targets the v0.20 releaseTargets the v0.20 release
Milestone
Description
Summary
seamless users prepare-device-replacement <id> cannot succeed against any current auth API. POST /admin/users/:userId/recovery/device-replacement has two requirements the CLI does not meet:
- Fresh step-up. The route runs
requireAdmin('write')andrequireStepUp()(src/routes/admin.routes.tsin seamless-auth-api). Without a step-up on the session in the last 5 minutes (DEFAULT_STEP_UP_MAX_AGE_SECONDS) it answers403 step_up_required. The CLI has no step-up support, andprepareDeviceReplacement(src/core/admin.ts) maps that 403 to "requires an elevated step-up admin session, which the CLI cannot establish yet. Use the dashboard for this action." - Identity proofing. Since
@seamless-auth/types0.9.0 (fells-code/seamless-auth-types,feat(admin): require identity proofing on device replacement recovery),DeviceReplacementRecoverySchemarequires aproofingobject, and the controller rejects a body without it with400 Invalid recovery payload. The CLI sends onlyrevokeSessions,removePasskeysanddisableTotp, so it would fail even with step-up in place.
The proofing contract, from RecoveryProofingSchema:
method:in_personorremote_exceptionevidenceRef: 1 to 200 characters, a pointer such as a ticket or case number, not the evidence itself (it is written to the audit trail)approver: 1 to 200 characters, required whenmethodisremote_exception
Found while moving the admin module onto the shared schemas (#144, #244). That PR keeps DeviceReplacementOptions as a local type so this gap stays visible.
Proposal
- Step-up over TOTP. Before the recovery call, prompt for the admin's TOTP code and
POST /totp/verify-mfaon the same session, then retry. WebAuthn step-up (/step-up/webauthn/*) needs a browser ceremony, so it stays dashboard-only. If the admin has no TOTP enrolled, keep pointing at the dashboard. Accept the code from a flag or stdin as well as the prompt, so the command still works underrequireInteractive. - Proofing flags.
--proofing in_person|remote_exceptionand--evidence <ref>, both required--approver <name>when the method isremote_exception- Validate locally with
RecoveryProofingSchema, so a missing approver is named before any request - Prompt for missing values interactively; under
--yes/ non-TTY, fail naming the flag
- Type the request with
DeviceReplacementRecoveryRequestfrom@seamless-auth/typesin place of the localDeviceReplacementOptions. - Name the proofing method and evidence reference in the confirmation prompt, since they go on the audit trail.
Done when
prepare-device-replacementcompletes against a current instance for an admin with TOTP enrolled, and prints the revoked session, removed credential and disabled TOTP counts.- A
remote_exceptionwithout--approverfails locally, naming the flag. - An admin without TOTP gets a clear pointer to the dashboard rather than a raw 403.
- Tests cover the step-up retry, the proofing validation, and the non-interactive path.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
P2SoonSoonarea:cliSeamless CLISeamless CLIbugSomething isn't workingSomething isn't workingsafeServes the safe promise: no lockout, no lock-in, honestServes the safe promise: no lockout, no lock-in, honestv0.20Targets the v0.20 releaseTargets the v0.20 release
Activity
Bccorb commentedon Oct 17, 2026
Milestoned v0.20 with recovery (2026-10-13). The interim behaviour is already in place: the step-up 403 is mapped to a message pointing at the dashboard, so the command fails clearly rather than with a raw error.
Bccorb commentedon Oct 22, 2026
Widened by fells-code/seamless-auth-api#490 (#437): every admin write now needs a fresh step-up, not only prepare-device-replacement. Affected commands:
users delete, all org and member writes,migrate(POST /admin/users/import, which also needs a fresh step-up whenever the 5-minute window lapses mid-run, since it sends 200-row batches), system config patch, and OAuth provider create/update/delete.call()insrc/core/admin.tsmaps any 403 to a genericPermissionError;step_up_requiredneeds its own message and the TOTP step-up flow (POST /totp/verify-mfaon the same session). Admins without TOTP cannot make these writes from the CLI until a passkey step-up path exists for it.