From aa3df74fc1c0cf446ebf330c56b3fd77112a7886 Mon Sep 17 00:00:00 2001 From: Bruno Charest Date: Tue, 8 Sep 2026 00:48:20 -0400 Subject: [PATCH] fix(mfa): /mfa/webauthn/options enumeration-safe (200 totp fallback au lieu de 400) + test --- backend/auth/router.py | 10 ++++------ tests/test_webauthn.py | 7 +++++++ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/backend/auth/router.py b/backend/auth/router.py index fa1b893..9e0c132 100644 --- a/backend/auth/router.py +++ b/backend/auth/router.py @@ -565,15 +565,13 @@ async def mfa_webauthn_remove( async def mfa_webauthn_login_options(body: dict = Body(...)): """Unauthenticated: begin the login assertion for a user with registered keys. - Returns null options (mfa_method 'totp') when the user has no WebAuthn keys. + Enumeration-safe: always 200 — returns null options (caller falls back to + TOTP/recovery UI) when the user has no WebAuthn key or MFA is off. """ username = str(body.get("username", "")) user = get_user(username) - if not user or not user.get("mfa_enabled"): - raise HTTPException(400, "MFA non activé pour cet utilisateur") - - creds = user.get("webauthn_credentials", []) - if not creds: + creds = (user or {}).get("webauthn_credentials", []) + if not user or not user.get("mfa_enabled") or not creds: return {"mfa_method": "totp", "options": None} from .webauthn_mfa import begin_authentication diff --git a/tests/test_webauthn.py b/tests/test_webauthn.py index 4c5c997..bc0dae2 100644 --- a/tests/test_webauthn.py +++ b/tests/test_webauthn.py @@ -309,6 +309,13 @@ class TestWebauthnApi: json={"username": "testuser", "credential": assertion}) assert v2.status_code == 401 + def test_login_options_enumeration_safe(self, wa_client): + # Unknown user / no MFA -> always 200 with totp fallback, no 404/400 leak + r = wa_client.post("/api/auth/mfa/webauthn/options", + json={"username": "ghost-user"}) + assert r.status_code == 200 + assert r.json() == {"mfa_method": "totp", "options": None} + def test_remove_key_disables_mfa(self, wa_client): headers = _login_headers(wa_client) options = wa_client.post("/api/auth/mfa/webauthn/register/options",