Enforce TOTP second factor on SSO login (#31) #39
Reference in New Issue
Block a user
No description provided.
Delete Branch "bugfix/sso-totp-bypass-31"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #31.
Problem
SSO login (
finishLogin) minted a full session immediately, ignoring the account'stotp_enabledflag. An admin who deliberately enabled 2FA and has a Google/Discord/OIDC identity linked could sign in through the SSO button and get an identical fully-privileged session without presenting their authenticator code — silently downgrading the account to the strength of the external IdP login. The local password flow already gates onneedsTotp(); SSO did not, leaving an asymmetric, un-hardened path to the same session.Fix
Wire SSO through the same staged-TOTP gate the local login uses (the issue's recommended option). Because SSO is a browser redirect flow rather than the local JSON flow, the second factor material is kept
httpOnlythroughout — the challenge never lands in a URL, browser history, referer, or JS.ssoState.js—createTotpPending/verifyTotpPending+ a short-lived (5m) httpOnlysso_totpcookie. The pending token carriesstage:'totp'(session validation rejects it, same marker as the local challenge / #32) andkind:'sso_totp'(scopes it to the SSO endpoint), plus the resolved context (userId, provider, authMethod, returnTo).sso.controller.js—finishLoginnow stages the challenge and redirects to/admin/login?sso_totp=1instead of creating a session when the account has TOTP on. NewfinishSsoTotpverifies the code (backoff + bot-scoring on a wrong code, mirroringloginTotp) and only then mints the session.sso.routes.js—POST /auth/sso/totpbehind the samebackoffGuard → slowLogin → loginLimiterstack and code validation as the local TOTP endpoint.AdminLogindetects?sso_totp=1, jumps to the existing code-entry step, and completes over fetch viaapi.ssoLoginTotp/AuthContext.ssoLoginTotp, then navigates to the server-returnedreturnTo.Testing
fail 0), including 12 new ones: TOTP gate stages a challenge with no session; correct code issues a session and clears the cookie; wrong code → 401; missing/expired cookie → 401; and a plain session token or a bare local/login/totpchallenge is not accepted by the SSO endpoint.vite build, exit 0).🤖 Generated with Claude Code
https://claude.ai/code/session_019rao86n5cXpwAyjdBFEshV