feat(auth): enforce the TOTP requirement - #55
Merged
Merged
Conversation
With security.totp_required on, a console session whose user has not enrolled TOTP (password or SSO sign-in) only reaches /api/auth/me, register-key, logout and the TOTP status/setup/verify-setup endpoints. Everything else answers 403 with error type totp_enrollment_required. The gate is decided per request in require_auth, so switching the setting on covers existing sessions and enrolling lifts it on the same session. API keys are unaffected. /api/auth/me reports totp_enrollment_required; the console replaces itself with an enrollment screen while it is set and reloads the user when any request is refused with that type. Disabling TOTP is refused while the setting is on. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Behaviour
security.totp_requiredis now enforced server-side instead of only driving a notice.When the setting is on and a user who has not enrolled TOTP signs in to the console (password login or SSO/OIDC callback), they still get a session, but that session can only reach:
GET /api/auth/mePOST /api/auth/register-key(the signing key every signed write depends on, including the enrollment calls)POST /api/auth/logoutGET /api/auth/totp/statusPOST /api/auth/totp/setupPOST /api/auth/totp/verify-setupEvery other console/admin endpoint answers:
(new
AppError::TotpEnrollmentRequiredvariant, same body shape as the otherAppErrors).require_auth, reusing the per-requestuserslookup that already checksis_active(it now also readstotp_enabled; no extra query). Switching the setting on applies to existing sessions; enrolling lifts the restriction on the same session, no re-login or token refresh.password_change_requiredturned out to have no server-side session gate to mirror; it is only a hint in the login response, so this is its own mechanism.)require_auth, and consoletw-API keys take the API-key branch before the gate.POST /api/auth/totp/disableis refused (400) while the setting is on; disabling would only put the session straight back at enrollment.GET /api/auth/megainstotp_enrollment_required: bool.Console
me.totp_enrollment_requiredis true, the root renders a standalone enrollment screen in place of the app shell (no navigation; the account email and a logout button are the only other things on it). After verify-setup succeeds the user is reloaded and the console appears.components/auth/totp-enrollment.tsxand shared by both places.totp_enrollment_required403; the auth hook reloads the user, so a session whose setting flips mid-session is sent to enrollment.errors.byType.totp_enrollment_requiredadded to the i18n checker's dynamic list.Tests
crates/test-support/tests/totp_required.rs/meand/totp/statuswork;/api/keys,/api/dashboard/stats,/api/health,/api/admin/settings,/api/admin/users,POST /api/keys,POST /api/auth/password→ 403totp_enrollment_required; real setup + verify-setup with a computed code lifts it on the same session; disable is then refused; logout works for an unenrolled sessionadmin_access.rs::an_unenrolled_sso_user_is_held_at_totp_enrollment— SSO sign-in against the mock IdP is held the same way and released by enrollingapi.test.ts(event fired only for this type),use-auth.test.tsx(user reloaded on the event and after enrolling),totp-enrollment.test.tsx(screen offers only enrollment and logout)Local:
cargo fmt --all --check,cargo clippy --workspace --all-targets -D warnings,cargo clippy --workspace --lib -D warnings,cargo nextest run --workspace --lib --bins --tests, the full integration suite (--run-ignored only --profile ci), andpnpm check:i18n && pnpm test && pnpm buildall pass. The enrollment screen and the mid-session switch were also checked in a browser against a mock API.🤖 Generated with Claude Code