fix: corrige 6 bugs mineurs (BUG-035 a BUG-040)
CI / lint (push) Successful in 1m36s
CI / security (push) Successful in 1m4s
CI / test (push) Successful in 3m41s
CI / build (push) Successful in 59s
CI / e2e (push) Successful in 11m8s

This commit is contained in:
2026-09-17 20:05:08 -04:00
parent 2c460022f8
commit 2e2a33cef3
25 changed files with 595 additions and 60 deletions
+18 -17
View File
@@ -124,31 +124,32 @@ async def auth_status():
async def login(body: LoginRequest, response: Response, request: Request):
"""Authenticate a user. Returns access token and sets refresh cookie.
Implements timing-safe responses to prevent user enumeration:
a failed login with an unknown user takes the same time as one
with a known user (dummy hash is computed).
Implements timing-safe responses to prevent user enumeration: a failed
login with an unknown user takes the same time as one with a known user
(dummy hash is computed). BUG-039: unknown, inactive, locked and
per-account rate-limited accounts all answer the same ``401`` so the HTTP
status can never reveal whether an account exists.
"""
client_ip = get_client_ip(request)
# IP-based rate limiting (10 failures / 15 min per IP). It is not
# account-specific, so a 429 here cannot be used to enumerate accounts.
if is_rate_limited(client_ip):
raise HTTPException(429, "Trop de tentatives depuis cette adresse IP (15min)")
user = get_user(body.username)
if not user:
# BUG-039: uniform 401 + equivalent timing for every account-state outcome.
if not user or not user.get("active"):
# Timing-safe: simulate hash computation to prevent user enumeration
hash_password("dummy_timing_protection")
raise HTTPException(401, "Identifiants invalides")
if not user.get("active"):
raise HTTPException(403, "Compte désactivé")
# IP-based rate limiting (10 failures / 15 min per IP)
client_ip = get_client_ip(request)
if is_rate_limited(client_ip):
raise HTTPException(429, "Trop de tentatives depuis cette adresse IP (15min)")
# BUG-031: per-account budget still applies when the attacker rotates IPs.
if is_account_rate_limited(body.username):
raise HTTPException(429, "Trop de tentatives sur ce compte (15min)")
if is_locked(body.username):
raise HTTPException(429, "Compte temporairement verrouillé (15min)")
# Kept indistinguishable from a wrong password (BUG-039).
if is_account_rate_limited(body.username) or is_locked(body.username):
hash_password("dummy_timing_protection")
raise HTTPException(401, "Identifiants invalides")
if not verify_password(body.password, user["password_hash"]):
attempts = record_login_failure(body.username)