Make TOTP OTP enforcement configurable per authentication path (password vs hook/sToken)
- Dominant language
- Python
- Stars
- 670
- Forks
- 183
- Avg merge
- 17h 7m
- Merged PRs (30d)
- 358
Description
## Background
When the keypair auth plugin and the TOTP plugin are both active, a TOTP-enabled user can authenticate via two different paths during the AUTHORIZE phase:
- \***Password-based login**\* — the user proves identity with a password.
- \***Hook-authenticated login (sToken / token-login)**\* — an AUTHORIZE hook plugin authenticates the user directly (SSO / edu-app redirect). When the hook returns a user, that user is already trusted.
Today the TOTP plugin's POST_AUTHORIZE handler (`validate_otp`) enforces OTP unconditionally for every TOTP-activated user, with no awareness of which auth path was taken in AUTHORIZE. As a result, hook-authenticated logins (which have no UI to input an OTP code) are required to provide an OTP and cannot complete. This is not a defect in a single flow but a missing capability: there is no way to express which authentication paths should trigger TOTP enforcement.
## Goal
Allow operators to configure, per authentication path, whether the TOTP plugin enforces OTP verification. For example, enforce TOTP on password-based logins while skipping it on hook-authenticated (sToken) logins, or any other combination the deployment requires.
## Scope
- Introduce a TOTP plugin configuration option (`TOTPConfig`) that selects which auth methods OTP verification applies to (e.g. `password`, `hook`).
- Propagate the authentication method determined in the AUTHORIZE phase down to the POST_AUTHORIZE hook so the plugin can make the decision. In `_verify_user`, a non-empty hook result means the user was hook-authenticated; otherwise the user was password-authenticated.
- `validate_otp` consults the configured set of enforced auth methods before requiring/validating OTP.
## Relevant Code
- `src/ai/backend/manager/plugin/totp/hook.py` — `validate_otp` (POST_AUTHORIZE handler) and `TOTPConfig`.
- `src/ai/backend/manager/services/auth/service.py` — `_verify_user` (AUTHORIZE dispatch; distinguishes hook vs password auth) and `_post_check` (POST_AUTHORIZE dispatch; currently passes no auth-method signal).
- `src/ai/backend/web/server.py` — `token_login_handler` (sToken token-login path).
## Acceptance Criteria
- TOTP enforcement can be configured per authentication path via plugin config.
- With the default/recommended configuration, a TOTP-enabled user logging in via sToken succeeds without OTP input, while password-based login of the same user still requires OTP.
- Behavior is driven by configuration, not hardcoded per-flow special-casing.
## Related
- BA-5791: Fix TOTP login failure due to OTP value leaking into sToken (separate bug, already fixed).
JIRA Issue: BA-5792
Contributor guide
Assessment
This issue has not been assessed yet.