diff --git a/backend/app/security.py b/backend/app/security.py new file mode 100644 index 0000000..9e39ff2 --- /dev/null +++ b/backend/app/security.py @@ -0,0 +1,45 @@ +from datetime import datetime, timedelta, timezone +from typing import Any + +from argon2 import PasswordHasher +from argon2.exceptions import InvalidHashError, VerificationError, VerifyMismatchError +from jose import JWTError, jwt + +from .config import settings + +ALGORITHM = "HS256" +_password_hasher = PasswordHasher() + + +def hash_password(password: str) -> str: + """Создаёт Argon2id-хеш пользовательского пароля.""" + return _password_hasher.hash(password) + + +def verify_password(password: str, password_hash: str) -> bool: + """Проверяет пароль и безопасно обрабатывает повреждённые хеши.""" + try: + return _password_hasher.verify(password_hash, password) + except (InvalidHashError, VerificationError, VerifyMismatchError): + return False + + +def create_access_token(subject: str) -> str: + """Создаёт короткоживущий JWT с идентификатором пользователя.""" + expires_at = datetime.now(timezone.utc) + timedelta( + minutes=settings.access_token_expire_minutes + ) + payload: dict[str, Any] = {"sub": subject, "exp": expires_at} + return jwt.encode(payload, settings.secret_key.get_secret_value(), algorithm=ALGORITHM) + + +def decode_access_token(token: str) -> dict[str, Any] | None: + """Проверяет подпись и срок действия JWT.""" + try: + return jwt.decode( + token, + settings.secret_key.get_secret_value(), + algorithms=[ALGORITHM], + ) + except JWTError: + return None