Update files

This commit is contained in:
glpshchn 2025-12-15 04:06:58 +03:00
parent 354d9aaedd
commit 453d2bf6d2
1 changed files with 61 additions and 8 deletions

View File

@ -5,6 +5,7 @@ from datetime import datetime, timedelta
from typing import Optional from typing import Optional
from jose import JWTError, jwt from jose import JWTError, jwt
from passlib.context import CryptContext from passlib.context import CryptContext
import bcrypt
from fastapi import HTTPException, status, Depends, Cookie, Header from fastapi import HTTPException, status, Depends, Cookie, Header
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
@ -12,7 +13,12 @@ from config import settings
from database import users_collection, moderation_admins_collection from database import users_collection, moderation_admins_collection
# Password hashing # Password hashing
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") # Используем bcrypt с явной обрезкой паролей до 72 байт
pwd_context = CryptContext(
schemes=["bcrypt"],
deprecated="auto",
bcrypt__ident="2b" # Используем современный формат bcrypt
)
# JWT bearer # JWT bearer
security = HTTPBearer(auto_error=False) security = HTTPBearer(auto_error=False)
@ -50,10 +56,51 @@ def hash_password(password: str) -> str:
else: else:
print(f"[Auth] ✅ Пароль в пределах лимита (72 байта)") 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: try:
result = pwd_context.hash(password) # Используем прямой вызов bcrypt для обхода проблемы с passlib
print(f"[Auth] ✅ Пароль успешно захеширован") # Обрезаем пароль до 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 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: except Exception as e:
print(f"[Auth] ❌ Ошибка при хешировании: {type(e).__name__}: {e}") print(f"[Auth] ❌ Ошибка при хешировании: {type(e).__name__}: {e}")
import traceback 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 # Bcrypt has a maximum password length of 72 bytes
# Truncate if necessary (encode to bytes first to get accurate byte length) # Truncate if necessary (encode to bytes first to get accurate byte length)
password_bytes = plain_password.encode('utf-8') password_bytes = plain_password.encode('utf-8')
if len(password_bytes) > 72: password_bytes_final = password_bytes[:72]
# Обрезаем до первых 72 байт (так же, как при хешировании)
plain_password = password_bytes[:72].decode('utf-8', errors='ignore') # Используем прямой вызов bcrypt для проверки
print(f"[Auth] ⚠️ Пароль при проверке обрезан до 72 байт (было {len(password_bytes)} байт)") try:
return pwd_context.verify(plain_password, hashed_password) 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: def create_access_token(user_id: str, expires_delta: Optional[timedelta] = None) -> str: