- sanitizer XSS serveur (markdown + page de partage) [BUG-021/022] - rate-limit/lockout MFA [BUG-023] - isolation vaults par segments [BUG-024] - caps regex ReDoS [BUG-025] - SSRF webhooks + secrets externalises [BUG-026] - rotation/revocation des jetons [BUG-027] - politique de mot de passe + invalidation sessions [BUG-028] - verrous users.json [BUG-029] - IP reelle dans les audits [BUG-030] - rate-limit par compte [BUG-031] - symlinks hors vault ignores [BUG-032] - recherche simple via inverted index [BUG-033] - token en memoire + cookie HttpOnly, CSP durcie [BUG-034] Tests: pytest 961 passed / 6 skipped, ruff 0, mypy 0, frontend vert.
59 lines
1.7 KiB
Python
59 lines
1.7 KiB
Python
# backend/auth/password.py
|
|
# Argon2id password hashing — OWASP 2024 recommended algorithm.
|
|
# Parameters: time_cost=2, memory_cost=64MB, parallelism=2
|
|
|
|
from argon2 import PasswordHasher
|
|
from argon2.exceptions import VerificationError, VerifyMismatchError
|
|
|
|
ph = PasswordHasher(
|
|
time_cost=2,
|
|
memory_cost=65536, # 64 MB
|
|
parallelism=2,
|
|
hash_len=32,
|
|
salt_len=16,
|
|
)
|
|
|
|
# Password policy (BUG-028). Applied by the API validators at account creation
|
|
# and password change so the rules stay consistent across both paths.
|
|
MIN_PASSWORD_LENGTH = 8
|
|
MAX_PASSWORD_LENGTH = 128
|
|
|
|
|
|
def validate_password_strength(password: str) -> str:
|
|
"""Validate a plaintext password against the project policy.
|
|
|
|
Args:
|
|
password: Candidate password.
|
|
|
|
Returns:
|
|
The password unchanged when valid.
|
|
|
|
Raises:
|
|
ValueError: When the password is too short, too long or blank.
|
|
"""
|
|
if password is None or len(password) < MIN_PASSWORD_LENGTH:
|
|
raise ValueError(f"Minimum {MIN_PASSWORD_LENGTH} caractères")
|
|
if len(password) > MAX_PASSWORD_LENGTH:
|
|
raise ValueError(f"Maximum {MAX_PASSWORD_LENGTH} caractères")
|
|
if not password.strip():
|
|
raise ValueError("Le mot de passe ne peut pas être vide")
|
|
return password
|
|
|
|
|
|
def hash_password(password: str) -> str:
|
|
"""Hash a password with Argon2id."""
|
|
return ph.hash(password)
|
|
|
|
|
|
def verify_password(password: str, hashed: str) -> bool:
|
|
"""Verify a password against its Argon2id hash."""
|
|
try:
|
|
return ph.verify(hashed, password)
|
|
except (VerifyMismatchError, VerificationError):
|
|
return False
|
|
|
|
|
|
def needs_rehash(hashed: str) -> bool:
|
|
"""Check if hash needs updating (parameters changed)."""
|
|
return ph.check_needs_rehash(hashed)
|