fix(mfa): /mfa/webauthn/options enumeration-safe (200 totp fallback au lieu de 400) + test
CI / lint (push) Successful in 40s
CI / security (push) Successful in 27s
CI / test (push) Successful in 53s
CI / build (push) Successful in 23s
CI / e2e (push) Successful in 6m1s
Desktop Build / build-windows (push) Canceled after 0s
Desktop Build / build-linux (push) Canceled after 0s
CI / lint (push) Successful in 40s
CI / security (push) Successful in 27s
CI / test (push) Successful in 53s
CI / build (push) Successful in 23s
CI / e2e (push) Successful in 6m1s
Desktop Build / build-windows (push) Canceled after 0s
Desktop Build / build-linux (push) Canceled after 0s
This commit is contained in:
@@ -565,15 +565,13 @@ async def mfa_webauthn_remove(
|
|||||||
async def mfa_webauthn_login_options(body: dict = Body(...)):
|
async def mfa_webauthn_login_options(body: dict = Body(...)):
|
||||||
"""Unauthenticated: begin the login assertion for a user with registered keys.
|
"""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", ""))
|
username = str(body.get("username", ""))
|
||||||
user = get_user(username)
|
user = get_user(username)
|
||||||
if not user or not user.get("mfa_enabled"):
|
creds = (user or {}).get("webauthn_credentials", [])
|
||||||
raise HTTPException(400, "MFA non activé pour cet utilisateur")
|
if not user or not user.get("mfa_enabled") or not creds:
|
||||||
|
|
||||||
creds = user.get("webauthn_credentials", [])
|
|
||||||
if not creds:
|
|
||||||
return {"mfa_method": "totp", "options": None}
|
return {"mfa_method": "totp", "options": None}
|
||||||
|
|
||||||
from .webauthn_mfa import begin_authentication
|
from .webauthn_mfa import begin_authentication
|
||||||
|
|||||||
@@ -309,6 +309,13 @@ class TestWebauthnApi:
|
|||||||
json={"username": "testuser", "credential": assertion})
|
json={"username": "testuser", "credential": assertion})
|
||||||
assert v2.status_code == 401
|
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):
|
def test_remove_key_disables_mfa(self, wa_client):
|
||||||
headers = _login_headers(wa_client)
|
headers = _login_headers(wa_client)
|
||||||
options = wa_client.post("/api/auth/mfa/webauthn/register/options",
|
options = wa_client.post("/api/auth/mfa/webauthn/register/options",
|
||||||
|
|||||||
Reference in New Issue
Block a user