docs(security): note that required-MFA prompts but does not wall the API

The "Require a second factor" policy redirects web users to enrollment but does
not block direct API or native (Bearer) access for an account that has not yet
enrolled — a deliberate lockout-safety choice. Make that explicit: spell it out
in the admin toggle description, add a code comment at the toggle, and add an
operator runbook (docs/ops/mfa-enforcement.md) describing the boundary.
This commit is contained in:
Marc-André Bombeck committed 2026-06-27 22:23:54 +02:00
1 parent 7e63794e81
commit 36c899b1c8
8 files changed
+43 -7

No files matched your search

+34
View File
@@ -0,0 +1,34 @@
# Require a second factor (operator)
The admin settings expose a **Require a second factor** policy — instance-wide
under General settings, and as a per-user override on the user detail page. The
effective requirement for an account is the OR of the per-user override and the
instance-wide policy.
## What enforcement does — and does not — do
Enforcement is a **guidance nudge for web sessions, not a hard API wall.**
- When the policy is on, any account that signs in **on the web** without an
active second factor is redirected to enrollment after authentication. It
cannot reach the rest of the app until it enrolls.
- Enforcement does **not** block direct API or native (Bearer-token) access for
an account that has not yet enrolled. A token-authenticated client keeps
working until the user enrolls a factor of their own accord.
This is deliberate. A hard API wall on a not-yet-enrolled account would lock a
native client (or an automation using a long-lived token) out of its own data
with no in-band way to enrol, and there is no server-side path to mint a second
factor on the user's behalf. The step-up gates that protect destructive actions
(MFA disable, recovery-code regeneration, account deletion, encrypted export)
remain cookie-only and are unaffected by this policy — they apply to accounts
that have actually enrolled a factor.
## Operator guidance
- Treat the policy as a strong prompt that drives adoption, not as a control
that retroactively secures every transport.
- To hard-gate a specific account's API access, the lever is the account
itself (disable it, or rotate/revoke its tokens), not the MFA-required flag.
- The redirect clears the moment the user completes enrollment; no operator
action is needed afterwards.
+1 -1
View File
@@ -5689,7 +5689,7 @@
"carrier": "Anbieter",
"carrierUnavailableGeoFallback": "{location} — Anbieter nicht verfügbar",
"mfaRequired": "Zweiten Faktor verlangen",
"mfaRequiredDescription": "Jedes Konto ohne aktiven zweiten Faktor wird nach der Anmeldung zur Einrichtung geleitet.",
"mfaRequiredDescription": "Jedes Konto ohne aktiven zweiten Faktor wird nach der Anmeldung zur Einrichtung geleitet. Das fordert Web-Nutzer zur Einrichtung auf – es blockiert jedoch keinen direkten API- oder nativen (Bearer-)Zugriff für noch nicht eingerichtete Konten.",
"mfaEnforcedOn": "MFA an",
"mfaEnforcedOff": "MFA aus",
"mfaEnforcedOnHint": "Für diesen Nutzer ist ein zweiter Faktor erforderlich. Klicken, um die Anforderung aufzuheben.",
+1 -1
View File
@@ -5689,7 +5689,7 @@
"carrier": "Carrier",
"carrierUnavailableGeoFallback": "{location} — carrier unavailable",
"mfaRequired": "Require a second factor",
"mfaRequiredDescription": "Every account without an active second factor is sent to enrollment after sign-in.",
"mfaRequiredDescription": "Every account without an active second factor is sent to enrollment after sign-in. This prompts web users to enroll — it does not block direct API or native (Bearer) access for accounts not yet enrolled.",
"mfaEnforcedOn": "MFA on",
"mfaEnforcedOff": "MFA off",
"mfaEnforcedOnHint": "A second factor is required for this user. Click to stop requiring it.",
+1 -1
View File
@@ -5689,7 +5689,7 @@
"carrier": "Operador",
"carrierUnavailableGeoFallback": "{location} — operador no disponible",
"mfaRequired": "Exigir un segundo factor",
"mfaRequiredDescription": "Toda cuenta sin un segundo factor activo se dirige a la configuración tras iniciar sesión.",
"mfaRequiredDescription": "Cada cuenta sin un segundo factor activo se dirige a la configuración tras iniciar sesión. Esto pide a los usuarios web que lo configuren, pero no bloquea el acceso directo por API o nativo (Bearer) de las cuentas que aún no lo tienen.",
"mfaEnforcedOn": "MFA activado",
"mfaEnforcedOff": "MFA desactivado",
"mfaEnforcedOnHint": "Este usuario debe usar un segundo factor. Haz clic para dejar de exigirlo.",
+1 -1
View File
@@ -5689,7 +5689,7 @@
"carrier": "Opérateur",
"carrierUnavailableGeoFallback": "{location} — opérateur indisponible",
"mfaRequired": "Exiger un second facteur",
"mfaRequiredDescription": "Tout compte sans second facteur actif est dirigé vers la configuration après la connexion.",
"mfaRequiredDescription": "Chaque compte sans deuxième facteur actif est dirigé vers la configuration après la connexion. Cela invite les utilisateurs web à l'activer, mais ne bloque pas l'accès direct par API ou natif (Bearer) pour les comptes pas encore configurés.",
"mfaEnforcedOn": "MFA activé",
"mfaEnforcedOff": "MFA désactivé",
"mfaEnforcedOnHint": "Un second facteur est requis pour cet utilisateur. Cliquez pour ne plus l'exiger.",
+1 -1
View File
@@ -5689,7 +5689,7 @@
"carrier": "Operatore",
"carrierUnavailableGeoFallback": "{location} — operatore non disponibile",
"mfaRequired": "Richiedi un secondo fattore",
"mfaRequiredDescription": "Ogni account senza un secondo fattore attivo viene indirizzato alla configurazione dopo l'accesso.",
"mfaRequiredDescription": "Ogni account senza un secondo fattore attivo viene indirizzato alla configurazione dopo l'accesso. Questo invita gli utenti web ad attivarlo, ma non blocca l'accesso diretto via API o nativo (Bearer) per gli account non ancora configurati.",
"mfaEnforcedOn": "MFA attivo",
"mfaEnforcedOff": "MFA disattivato",
"mfaEnforcedOnHint": "Per questo utente è richiesto un secondo fattore. Clicca per non richiederlo più.",
+1 -1
View File
@@ -5689,7 +5689,7 @@
"carrier": "Operator",
"carrierUnavailableGeoFallback": "{location} — operator niedostępny",
"mfaRequired": "Wymagaj drugiego składnika",
"mfaRequiredDescription": "Każde konto bez aktywnego drugiego składnika jest kierowane do konfiguracji po zalogowaniu.",
"mfaRequiredDescription": "Każde konto bez aktywnego drugiego składnika jest po zalogowaniu kierowane do konfiguracji. To zachęca użytkowników sieci do włączenia go, ale nie blokuje bezpośredniego dostępu przez API ani natywnego (Bearer) dla kont jeszcze nieskonfigurowanych.",
"mfaEnforcedOn": "MFA wł.",
"mfaEnforcedOff": "MFA wył.",
"mfaEnforcedOnHint": "Ten użytkownik musi używać drugiego składnika. Kliknij, aby przestać go wymagać.",
@@ -45,7 +45,9 @@ export function GeneralSettingsSection() {
{/* v1.23 — instance-wide "require a second factor" policy. Every
account without an active factor is sent to forced enrollment after
sign-in. */}
sign-in. This is a web-session nudge, not an API wall: direct API /
native (Bearer) access for a not-yet-enrolled account is not blocked.
See docs/ops/mfa-enforcement.md. */}
<SettingsToggle
label={t("admin.mfaRequired")}
description={t("admin.mfaRequiredDescription")}