""" =============================================================================== FILE: modules/web_api/routers/auth.py ROLE: Аутентификация, валидация JWT-токенов и управление паролями. =============================================================================== """ import logging from datetime import datetime, timedelta from typing import Dict, Any, Optional import jwt from passlib.context import CryptContext from fastapi import APIRouter, Depends, HTTPException, status from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from pydantic import BaseModel from core.connection import get_connection JWT_SECRET = "scud_jwt_secret_key_2026_orion_ai_super_secure" ALGORITHM = "HS256" pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") security = HTTPBearer() router = APIRouter(prefix="/api/v1/auth", tags=["auth"]) def get_db(): return get_connection(row_factory=True) def create_access_token(user_id: int, username: str, is_admin: bool) -> str: payload = { "sub": str(user_id), "username": username, "is_admin": is_admin, "exp": datetime.utcnow() + timedelta(days=30) } return jwt.encode(payload, JWT_SECRET, algorithm=ALGORITHM) def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)) -> Dict[str, Any]: try: token = credentials.credentials payload = jwt.decode(token, JWT_SECRET, algorithms=[ALGORITHM]) user_id = int(payload.get("sub")) username = payload.get("username") is_admin = bool(payload.get("is_admin", False)) return {"id": user_id, "username": username, "is_admin": is_admin} except Exception as e: logging.warning(f"Auth error: {e}") raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Недействительный или просроченный токен авторизации", headers={"WWW-Authenticate": "Bearer"}, ) class AuthRequest(BaseModel): username: str password: str class ChangePasswordRequest(BaseModel): old_password: str new_password: str @router.post("/login") def login(req: AuthRequest): username = req.username.strip().lower() conn = get_db() cursor = conn.cursor() cursor.execute("SELECT id, username, password_hash, is_admin FROM users WHERE username = ?", (username,)) user = cursor.fetchone() conn.close() if not user or not pwd_context.verify(req.password, user["password_hash"]): raise HTTPException(status_code=401, detail="Неверное имя пользователя или пароль") is_admin = bool(user["is_admin"]) or (user["username"] == "puh") token = create_access_token(user["id"], user["username"], is_admin) return {"status": "success", "token": token, "username": user["username"], "is_admin": is_admin} @router.post("/change-password") def change_password(req: ChangePasswordRequest, current_user: Dict[str, Any] = Depends(get_current_user)): if not req.new_password or len(req.new_password) < 4: raise HTTPException(status_code=400, detail="Новый пароль должен содержать минимум 4 символа") conn = get_db() cursor = conn.cursor() cursor.execute("SELECT password_hash FROM users WHERE id = ?", (current_user["id"],)) user = cursor.fetchone() if not user or not pwd_context.verify(req.old_password, user["password_hash"]): conn.close() raise HTTPException(status_code=400, detail="Неверный старый пароль") new_hash = pwd_context.hash(req.new_password) cursor.execute("UPDATE users SET password_hash = ? WHERE id = ?", (new_hash, current_user["id"])) conn.commit() conn.close() return {"status": "success", "message": "Пароль успешно изменен"}