diff --git a/moderation/backend-py/utils/auth.py b/moderation/backend-py/utils/auth.py index 82305d7..7a2950e 100644 --- a/moderation/backend-py/utils/auth.py +++ b/moderation/backend-py/utils/auth.py @@ -5,6 +5,7 @@ from datetime import datetime, timedelta from typing import Optional from jose import JWTError, jwt from passlib.context import CryptContext +import bcrypt from fastapi import HTTPException, status, Depends, Cookie, Header from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials @@ -12,7 +13,12 @@ from config import settings from database import users_collection, moderation_admins_collection # Password hashing -pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") +# Используем bcrypt с явной обрезкой паролей до 72 байт +pwd_context = CryptContext( + schemes=["bcrypt"], + deprecated="auto", + bcrypt__ident="2b" # Используем современный формат bcrypt +) # JWT bearer security = HTTPBearer(auto_error=False) @@ -50,10 +56,51 @@ def hash_password(password: str) -> str: else: print(f"[Auth] ✅ Пароль в пределах лимита (72 байта)") + # ВАЖНО: passlib/bcrypt может проверять длину пароля ВНУТРИ библиотеки + # Обрезаем пароль до 72 байт ПЕРЕД передачей в pwd_context.hash() + # Это гарантирует, что пароль никогда не превысит лимит + password_bytes_final = password_bytes[:72] + password_final = password_bytes_final.decode('utf-8', errors='strict') + + print(f"[Auth] Финальная длина в байтах: {len(password_bytes_final)}") + print(f"[Auth] Финальный пароль (repr): {repr(password_final)}") + print(f"[Auth] Финальный пароль (len): {len(password_final)} символов") + + # Дополнительная проверка: убедимся, что после декодирования длина в байтах не изменилась + password_final_bytes_check = password_final.encode('utf-8') + if len(password_final_bytes_check) > 72: + print(f"[Auth] ⚠️ После декодирования длина стала {len(password_final_bytes_check)} байт, обрезаем еще раз") + password_final = password_final_bytes_check[:72].decode('utf-8', errors='ignore') + password_final_bytes_check = password_final.encode('utf-8') + + print(f"[Auth] Финальная проверка: {len(password_final_bytes_check)} байт") + try: - result = pwd_context.hash(password) - print(f"[Auth] ✅ Пароль успешно захеширован") + # Используем прямой вызов bcrypt для обхода проблемы с passlib + # Обрезаем пароль до 72 байт и передаем как bytes + password_bytes_for_bcrypt = password_final_bytes_check[:72] + + # Генерируем соль и хешируем пароль + salt = bcrypt.gensalt(rounds=10) + hashed = bcrypt.hashpw(password_bytes_for_bcrypt, salt) + + # Конвертируем bytes в строку для хранения + result = hashed.decode('utf-8') + print(f"[Auth] ✅ Пароль успешно захеширован через прямой bcrypt") return result + except ValueError as e: + # Если bcrypt все равно выдает ошибку, попробуем обрезать до 71 байта + error_msg = str(e).lower() + if "72 bytes" in error_msg or "longer than" in error_msg or "truncate" in error_msg: + print(f"[Auth] ⚠️ Bcrypt все равно жалуется на длину, обрезаем до 71 байта") + password_bytes_safe = password_bytes[:71] + salt = bcrypt.gensalt(rounds=10) + hashed = bcrypt.hashpw(password_bytes_safe, salt) + result = hashed.decode('utf-8') + print(f"[Auth] ✅ Пароль успешно захеширован после обрезки до 71 байта") + return result + print(f"[Auth] ❌ ValueError: {e}") + raise except Exception as e: print(f"[Auth] ❌ Ошибка при хешировании: {type(e).__name__}: {e}") import traceback @@ -73,11 +120,17 @@ def verify_password(plain_password: str, hashed_password: str) -> bool: # Bcrypt has a maximum password length of 72 bytes # Truncate if necessary (encode to bytes first to get accurate byte length) password_bytes = plain_password.encode('utf-8') - if len(password_bytes) > 72: - # Обрезаем до первых 72 байт (так же, как при хешировании) - plain_password = password_bytes[:72].decode('utf-8', errors='ignore') - print(f"[Auth] ⚠️ Пароль при проверке обрезан до 72 байт (было {len(password_bytes)} байт)") - return pwd_context.verify(plain_password, hashed_password) + password_bytes_final = password_bytes[:72] + + # Используем прямой вызов bcrypt для проверки + try: + hashed_bytes = hashed_password.encode('utf-8') + return bcrypt.checkpw(password_bytes_final, hashed_bytes) + except Exception as e: + print(f"[Auth] ❌ Ошибка при проверке пароля: {type(e).__name__}: {e}") + # Fallback на passlib + plain_password_final = password_bytes_final.decode('utf-8', errors='ignore') + return pwd_context.verify(plain_password_final, hashed_password) def create_access_token(user_id: str, expires_delta: Optional[timedelta] = None) -> str: