From 144f4dc0a8a53eb0637089b4f1a3624efc66f4ad Mon Sep 17 00:00:00 2001 From: host Date: Fri, 24 Jul 2026 22:06:13 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D1=84=D0=B0=D0=B9=D0=BB=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/security.py | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/backend/app/security.py b/backend/app/security.py index 693bfe7..2d8480d 100644 --- a/backend/app/security.py +++ b/backend/app/security.py @@ -1,25 +1,41 @@ +"""Утилиты для паролей и JWT-токенов.""" + from datetime import datetime, timedelta from typing import Optional -from jose import jwt, JWTError -from passlib.context import CryptContext +import bcrypt +from jose import JWTError, jwt from .config import settings -pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") - ALGORITHM = "HS256" +# bcrypt ограничивает длину входа 72 байтами. Длиннее — обрезаем. +_BCRYPT_MAX_BYTES = 72 + + +def _normalize_for_bcrypt(password: str) -> bytes: + """bcrypt принимает максимум 72 байта. Длинные пароли обрезаем, + чтобы не получать ValueError при хешировании.""" + encoded = password.encode("utf-8") + return encoded[:_BCRYPT_MAX_BYTES] def hash_password(password: str) -> str: - return pwd_context.hash(password) + """Возвращает bcrypt-хеш пароля (ASCII-строка).""" + salt = bcrypt.gensalt(rounds=12) + return bcrypt.hashpw(_normalize_for_bcrypt(password), salt).decode("utf-8") def verify_password(plain: str, hashed: str) -> bool: - return pwd_context.verify(plain, hashed) + """Проверяет пароль против bcrypt-хеша.""" + try: + return bcrypt.checkpw(_normalize_for_bcrypt(plain), hashed.encode("utf-8")) + except (ValueError, TypeError): + return False def create_access_token(data: dict, expires_minutes: Optional[int] = None) -> str: + """Создаёт JWT с подписью HS256 и заданным сроком жизни.""" to_encode = data.copy() expire = datetime.utcnow() + timedelta( minutes=expires_minutes or settings.access_token_expire_minutes @@ -29,6 +45,7 @@ def create_access_token(data: dict, expires_minutes: Optional[int] = None) -> st def decode_access_token(token: str) -> Optional[dict]: + """Декодирует JWT. Возвращает None при любой ошибке (просрочен, подделан и т.п.).""" try: return jwt.decode(token, settings.secret_key, algorithms=[ALGORITHM]) except JWTError: