diff --git a/app/core/__init__.py b/app/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/core/passwords.py b/app/core/passwords.py new file mode 100644 index 0000000..01c9e4d --- /dev/null +++ b/app/core/passwords.py @@ -0,0 +1,50 @@ +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")