from argon2 import PasswordHasher from argon2.exceptions import InvalidHashError, VerifyMismatchError from argon2.low_level import Type from zxcvbn import zxcvbn from app.config import settings class WeakPasswordError(ValueError): pass _hasher = PasswordHasher( time_cost=3, memory_cost=65536, parallelism=4, hash_len=32, salt_len=16, type=Type.ID, ) def _peppered(password: str) -> str: return password + settings.password_pepper # возвращает хэш Argon2id от password + pepper; def hash_password(password: str) -> str: return _hasher.hash(_peppered(password)) # проверяет соответствие def verify_password(password: str, hashed: str) -> bool: try: return _hasher.verify(hashed, _peppered(password)) except (VerifyMismatchError, InvalidHashError): return False # проверяет политику def validate_policy(password: str, user_inputs: list[str] | None = None) -> None: # длина не меньше settings.password_min_length; if len(password) < settings.password_min_length: raise WeakPasswordError( f"Password must be >= {settings.password_min_length} chars" ) result = zxcvbn(password, user_inputs=user_inputs or []) # оценка стойкости через zxcvbn должна быть не ниже 3 (по шкале 0–4); if result["score"] < 3: raise WeakPasswordError("Password is too weak")