import time import secrets from datetime import datetime from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, func from passlib.context import CryptContext from app.models.models import User, LoginAttempt, AuditLog pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") BRUTE_FORCE_WINDOW = 180 BRUTE_FORCE_MAX_ATTEMPTS = 3 def hash_password(password: str) -> str: return pwd_context.hash(password) def verify_password(password: str, hashed: str) -> bool: return pwd_context.verify(password, hashed) def generate_csrf_token() -> str: return secrets.token_hex(32) async def brute_force_check(db: AsyncSession, login: str, ip_address: str) -> bool: cutoff = datetime.utcnow().timestamp() - BRUTE_FORCE_WINDOW result = await db.execute( select(func.count(LoginAttempt.id)) .where( (LoginAttempt.login == login) | (LoginAttempt.ip_address == ip_address), LoginAttempt.attempt_time > datetime.fromtimestamp(cutoff), ) ) count = result.scalar() or 0 return count >= BRUTE_FORCE_MAX_ATTEMPTS async def record_attempt(db: AsyncSession, login: str, ip_address: str): attempt = LoginAttempt(login=login, ip_address=ip_address) db.add(attempt) await db.flush() async def clear_attempts(db: AsyncSession, login: str, ip_address: str): from sqlalchemy import delete await db.execute( delete(LoginAttempt).where( (LoginAttempt.login == login) | (LoginAttempt.ip_address == ip_address) ) ) await db.flush() async def authenticate(db: AsyncSession, login: str, password: str, ip_address: str) -> dict: if await brute_force_check(db, login, ip_address): return {"success": False, "error": "Превышен лимит попыток. Подождите 3 минуты."} result = await db.execute( select(User).where(User.login == login).limit(1) ) user = result.scalar_one_or_none() if not user or not verify_password(password, user.password_hash): await record_attempt(db, login, ip_address) return {"success": False, "error": "Неверный логин или пароль."} if not user.is_active: return {"success": False, "error": "Учётная запись заблокирована."} await clear_attempts(db, login, ip_address) await audit_log(db, user.id, "login", "Вход в систему", ip_address) return { "success": True, "user": { "id": user.id, "login": user.login, "role": user.role, "full_name": user.full_name, }, } async def audit_log(db: AsyncSession, user_id: int | None, action: str, details: str, ip_address: str): try: log = AuditLog(user_id=user_id, action=action, details=details, ip_address=ip_address) db.add(log) await db.flush() except Exception: pass