2025-06-09 14:43:25 +05:00
|
|
|
# services/auth_service.py
|
|
|
|
|
from jose import JWTError, jwt
|
|
|
|
|
from datetime import datetime, timedelta
|
|
|
|
|
from fastapi import HTTPException, Depends, Request
|
|
|
|
|
from fastapi.security import OAuth2PasswordBearer
|
|
|
|
|
from models.auth import UserCreate, UserLogin, UserResponse, Token
|
|
|
|
|
from storage.json_storage import JsonStorage
|
|
|
|
|
from storage.session_storage import SessionStorage
|
|
|
|
|
from typing import Optional
|
|
|
|
|
import configparser
|
|
|
|
|
import os
|
|
|
|
|
import logging
|
|
|
|
|
|
|
|
|
|
logging.basicConfig(level=logging.INFO)
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
config = configparser.ConfigParser()
|
|
|
|
|
config_path = "/etc/gonets/config.ini"
|
|
|
|
|
if not os.path.exists(config_path):
|
|
|
|
|
raise RuntimeError(f"Конфигурационный файл {config_path} не найден")
|
|
|
|
|
config.read(config_path)
|
|
|
|
|
SECRET_KEY = config["JWT"]["secret_key"]
|
|
|
|
|
|
|
|
|
|
ALGORITHM = "HS256"
|
|
|
|
|
ACCESS_TOKEN_EXPIRE_MINUTES = 30
|
|
|
|
|
|
|
|
|
|
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/login")
|
|
|
|
|
|
2025-06-20 11:22:54 +05:00
|
|
|
|
2025-06-09 14:43:25 +05:00
|
|
|
class AuthService:
|
|
|
|
|
"""Сервис для регистрации, авторизации и управления сессиями"""
|
2025-06-20 11:22:54 +05:00
|
|
|
|
2025-06-09 14:43:25 +05:00
|
|
|
def __init__(self):
|
|
|
|
|
self.storage = JsonStorage()
|
|
|
|
|
self.session_storage = SessionStorage()
|
|
|
|
|
|
|
|
|
|
def create_access_token(self, data: dict) -> str:
|
|
|
|
|
"""Создаёт токен"""
|
|
|
|
|
to_encode = data.copy()
|
2025-06-20 11:22:54 +05:00
|
|
|
expire = datetime.utcnow() + timedelta(
|
|
|
|
|
minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
2025-06-09 14:43:25 +05:00
|
|
|
to_encode.update({"exp": expire})
|
|
|
|
|
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
|
|
|
|
return encoded_jwt
|
|
|
|
|
|
|
|
|
|
def verify_token(self, token: str) -> Optional[dict]:
|
|
|
|
|
"""Проверяет токен"""
|
|
|
|
|
try:
|
|
|
|
|
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
|
|
|
|
username: str = payload.get("sub")
|
|
|
|
|
if username is None:
|
|
|
|
|
return None
|
|
|
|
|
return {"username": username}
|
|
|
|
|
except JWTError:
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
def register(self, user: UserCreate) -> UserResponse:
|
|
|
|
|
"""Регистрирует нового пользователя"""
|
|
|
|
|
try:
|
|
|
|
|
db_user = self.storage.create_user(user)
|
|
|
|
|
return db_user
|
|
|
|
|
except ValueError as e:
|
2025-06-20 11:22:54 +05:00
|
|
|
logger.error(
|
|
|
|
|
f"Ошибка при регистрации пользователя {user.username}: {str(e)}"
|
|
|
|
|
)
|
2025-06-09 14:43:25 +05:00
|
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
|
except Exception as e:
|
2025-06-20 11:22:54 +05:00
|
|
|
logger.error(
|
|
|
|
|
f"Ошибка при регистрации пользователя {user.username}: {type(e).__name__} - {str(e)}"
|
|
|
|
|
)
|
2025-06-09 14:43:25 +05:00
|
|
|
raise HTTPException(status_code=500, detail="Ошибка регистрации")
|
|
|
|
|
|
|
|
|
|
def login(self, user: UserLogin, client_id: str) -> dict:
|
|
|
|
|
"""Авторизует пользователя, создаёт сессию и возвращает токен"""
|
|
|
|
|
try:
|
|
|
|
|
db_user = self.storage.get_user_by_username(user.username)
|
|
|
|
|
if not db_user:
|
|
|
|
|
logger.error(f"Пользователь {user.username} не найден")
|
2025-06-20 11:22:54 +05:00
|
|
|
raise HTTPException(status_code=401,
|
|
|
|
|
detail="Неверный логин или пароль")
|
|
|
|
|
if not self.storage.verify_password(user.password,
|
|
|
|
|
db_user["password_hash"]):
|
|
|
|
|
logger.error(
|
|
|
|
|
f"Неверный пароль для пользователя {user.username}")
|
|
|
|
|
raise HTTPException(status_code=401,
|
|
|
|
|
detail="Неверный логин или пароль")
|
|
|
|
|
access_token = self.create_access_token(
|
|
|
|
|
data={"sub": user.username})
|
|
|
|
|
session_id = self.session_storage.create_session(
|
|
|
|
|
user.username, access_token, client_id)
|
2025-06-09 14:43:25 +05:00
|
|
|
logger.info(f"Пользователь {user.username} успешно авторизован")
|
|
|
|
|
return {
|
2025-06-20 11:22:54 +05:00
|
|
|
"access_token":
|
|
|
|
|
access_token,
|
|
|
|
|
"token_type":
|
|
|
|
|
"bearer",
|
|
|
|
|
"session_id":
|
|
|
|
|
session_id,
|
|
|
|
|
"user":
|
|
|
|
|
UserResponse(
|
|
|
|
|
id=db_user["id"],
|
|
|
|
|
username=db_user["username"],
|
|
|
|
|
is_admin=db_user["is_admin"],
|
|
|
|
|
),
|
2025-06-09 14:43:25 +05:00
|
|
|
}
|
|
|
|
|
except Exception as e:
|
2025-06-20 11:22:54 +05:00
|
|
|
logger.error(
|
|
|
|
|
f"Ошибка при авторизации пользователя {user.username}: {type(e).__name__} - {str(e)}"
|
|
|
|
|
)
|
2025-06-09 14:43:25 +05:00
|
|
|
raise HTTPException(status_code=500, detail="Ошибка авторизации")
|
|
|
|
|
|
2025-06-20 11:22:54 +05:00
|
|
|
def get_current_user(
|
|
|
|
|
self, token: str = Depends(oauth2_scheme)) -> UserResponse:
|
2025-06-09 14:43:25 +05:00
|
|
|
"""Получает текущего пользователя по токену"""
|
|
|
|
|
credentials = self.verify_token(token)
|
|
|
|
|
if not credentials:
|
|
|
|
|
logger.error("Неверный или истёкший токен")
|
|
|
|
|
raise HTTPException(status_code=401, detail="Неверный токен")
|
|
|
|
|
username = credentials["username"]
|
|
|
|
|
db_user = self.storage.get_user_by_username(username)
|
|
|
|
|
if not db_user:
|
|
|
|
|
logger.error(f"Пользователь {username} не найден")
|
2025-06-20 11:22:54 +05:00
|
|
|
raise HTTPException(status_code=401,
|
|
|
|
|
detail="Пользователь не найден")
|
|
|
|
|
return UserResponse(id=db_user["id"],
|
|
|
|
|
username=db_user["username"],
|
|
|
|
|
is_admin=db_user["is_admin"])
|
2025-06-09 14:43:25 +05:00
|
|
|
|
|
|
|
|
def get_user_by_session(self, client_id: str) -> dict:
|
|
|
|
|
"""Получает пользователя и токен по client_id"""
|
|
|
|
|
session = self.session_storage.get_latest_session(client_id)
|
|
|
|
|
if not session:
|
2025-06-20 11:22:54 +05:00
|
|
|
logger.error(
|
|
|
|
|
f"Сессия для client_id {client_id} не найдена или истекла")
|
|
|
|
|
raise HTTPException(status_code=401,
|
|
|
|
|
detail="Сессия недействительна")
|
2025-06-09 14:43:25 +05:00
|
|
|
username = session["username"]
|
|
|
|
|
token = session["token"]
|
|
|
|
|
db_user = self.storage.get_user_by_username(username)
|
|
|
|
|
if not db_user:
|
|
|
|
|
logger.error(f"Пользователь {username} не найден")
|
2025-06-20 11:22:54 +05:00
|
|
|
raise HTTPException(status_code=401,
|
|
|
|
|
detail="Пользователь не найден")
|
2025-06-09 14:43:25 +05:00
|
|
|
return {
|
2025-06-20 11:22:54 +05:00
|
|
|
"user":
|
|
|
|
|
UserResponse(
|
|
|
|
|
id=db_user["id"],
|
|
|
|
|
username=db_user["username"],
|
|
|
|
|
is_admin=db_user["is_admin"],
|
|
|
|
|
),
|
|
|
|
|
"access_token":
|
|
|
|
|
token,
|
2025-06-09 14:43:25 +05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def logout(self, client_id: str):
|
|
|
|
|
"""Выполняет выход из системы, удаляя последнюю сессию для client_id"""
|
|
|
|
|
try:
|
|
|
|
|
session = self.session_storage.get_latest_session(client_id)
|
|
|
|
|
if session:
|
|
|
|
|
self.session_storage.delete_session(session["session_id"])
|
2025-06-20 11:22:54 +05:00
|
|
|
logger.info(
|
|
|
|
|
f"Пользователь успешно вышел из системы для client_id {client_id}"
|
|
|
|
|
)
|
2025-06-09 14:43:25 +05:00
|
|
|
return {"message": "Успешный выход из системы"}
|
|
|
|
|
except Exception as e:
|
2025-06-20 11:22:54 +05:00
|
|
|
logger.error(
|
|
|
|
|
f"Ошибка при выходе из системы: {type(e).__name__} - {str(e)}")
|
|
|
|
|
raise HTTPException(status_code=500,
|
|
|
|
|
detail="Ошибка при выходе из системы")
|