Skip to content

feat(admin): step up admin writes with an authenticator app code - #315

Merged
Bccorb merged 1 commit into
mainfrom
feat/admin-step-up
Oct 29, 2026
Merged

Bccorb merged 1 commit into
mainfrom
feat/admin-step-up

Conversation

@Bccorb

@Bccorb Bccorb commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

Closes #245

From auth server v0.20 (fells-code/seamless-auth-api#490) every admin write answers 403 {error: "step_up_required", ...status} until the session has a fresh step-up. The CLI mapped any 403 to "requires an admin role", so these commands failed with a misleading message.

What changes

  • Step-up lives in the instance client (src/core/authClient.ts). When a response is 403 step_up_required, the client runs a step-up handler and retries the same request once on the same session. Because it sits under every wrapper, it applies to each migrate batch, not only the first, so a run that outlasts the 5 minute window asks for a new code before the next batch. Concurrent refusals share one step-up, the same way refresh is single-flight. Portal clients never step up.
  • Authenticator app handler (src/core/stepUp.ts):
    • Checks GET /totp/status first. /totp/verify-mfa answers 401 totp_verification_failed for an account with no TOTP as well as for a wrong code, so the status call is the only way to tell them apart. With nothing enrolled, the CLI tells the user to enroll an authenticator app (dashboard or app) or use the dashboard.
    • Prompts on stderr (stdout stays clean for --json), validates the code with the shared TotpVerifyRequestSchema, and strips the spaces apps show.
    • 401 totp_verification_failed: re-prompts, at most 3 attempts.
    • 423 account_locked: reports retryAfterSeconds. 429: reports Retry-After.
    • No terminal: fails with a clear message. SEAMLESS_TOTP_CODE supplies a code without one (same pattern as SEAMLESS_REFRESH_TOKEN). It is used once per run because codes are single use.
  • Separate error. forbidden(res) gives a StepUpError (a PermissionError subclass, so existing handlers print it) for step_up_required, and the plain admin-role error otherwise. It is used in admin.ts call(), every systemConfig.ts wrapper and migrate.ts.
  • A rejected code is not an expired session. The client used to refresh on any 401, which would have retried a wrong code and then logged the user out. A 401 totp_verification_failed is now returned as is.
  • migrate: if a later batch fails (step-up refused or cancelled), the error carries the completed outcomes, so the report is still written.
  • prepare-device-replacement now sends the identity proofing that DeviceReplacementRecoverySchema requires: --proofing <in_person|remote_exception> --evidence-ref <ref> [--approver <name>], validated locally with RecoveryProofingSchema. Without it the v0.20 API rejects the request, so this is a breaking flag change (minor changeset, described in the body).
  • PermissionError moved to src/core/permissionError.ts to avoid an import cycle. It is still re-exported from systemConfig.ts and admin.ts.

Commands covered

config set, config apply, config oauth-providers add|update|remove, users delete, users prepare-device-replacement, org create|update, org members add|update|remove, migrate csv --apply and migrate entra-id (dry runs too, since the import route is gated).

Checks

  • npm run build: passes
  • npm test: 69 files passed (1 skipped), 1480 tests passed, 4 skipped
  • npm run coverage: passes thresholds (statements 98.88, branches 96.24, functions 98.36, lines 99.22)

Help topics (config, users, org, migrate), README and AGENTS.md are updated.

From auth server v0.20 every admin write answers 403 step_up_required until the session has a fresh step-up. The instance client now verifies an authenticator app code on the same session and retries the write once, for every command and every migrate batch. prepare-device-replacement sends the identity proofing record the server requires.
@Bccorb
Bccorb merged commit bf31ba6 into main Oct 29, 2026
6 checks passed
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(users): prepare-device-replacement needs step-up and identity proofing

1 participant