core/resolvers/auth.py

305 lines
12 KiB
Python
Raw Permalink Normal View History

2025-07-02 21:20:10 +00:00
"""
Auth резолверы - тонкие GraphQL обёртки над AuthService
"""
2025-07-02 19:30:21 +00:00
from typing import Any, Dict, List, Union
2023-10-26 21:07:35 +00:00
from graphql import GraphQLResolveInfo
2025-06-02 18:50:58 +00:00
from graphql.error import GraphQLError
2023-10-30 21:00:55 +00:00
2025-07-02 21:20:10 +00:00
from services.auth import auth_service
2025-07-02 19:30:21 +00:00
from services.schema import mutation, query, type_author
2025-07-02 21:20:10 +00:00
from settings import SESSION_COOKIE_NAME
2025-05-29 09:37:39 +00:00
from utils.logger import root_logger as logger
2025-07-02 19:30:21 +00:00
2025-07-02 21:20:10 +00:00
def handle_error(operation: str, error: Exception) -> GraphQLError:
"""Обрабатывает ошибки в резолверах"""
logger.error(f"Ошибка при {operation}: {error}")
return GraphQLError(f"Не удалось {operation}: {error}")
2025-07-02 19:30:21 +00:00
2025-07-02 21:20:10 +00:00
# === РЕЗОЛВЕР ДЛЯ ТИПА AUTHOR ===
@type_author.field("roles")
def resolve_roles(obj: Union[Dict, Any], info: GraphQLResolveInfo) -> List[str]:
"""Резолвер для поля roles автора"""
2025-07-02 19:30:21 +00:00
try:
if hasattr(obj, "get_roles"):
return obj.get_roles()
if isinstance(obj, dict):
roles_data = obj.get("roles_data", {})
if isinstance(roles_data, list):
return roles_data
if isinstance(roles_data, dict):
return roles_data.get("1", [])
return []
except Exception as e:
2025-07-02 21:20:10 +00:00
logger.error(f"Ошибка получения ролей: {e}")
2025-07-02 19:30:21 +00:00
return []
2022-09-03 10:50:14 +00:00
2025-07-02 21:20:10 +00:00
# === МУТАЦИИ АУТЕНТИФИКАЦИИ ===
2021-07-30 13:22:37 +00:00
2021-07-30 12:53:22 +00:00
@mutation.field("registerUser")
2025-07-02 21:20:10 +00:00
async def register_user(
_: None, _info: GraphQLResolveInfo, email: str, password: str = "", name: str = ""
) -> dict[str, Any]:
"""Регистрирует нового пользователя"""
2025-05-16 06:23:48 +00:00
try:
2025-07-02 21:20:10 +00:00
return await auth_service.register_user(email, password, name)
2025-05-16 06:23:48 +00:00
except Exception as e:
2025-07-02 21:20:10 +00:00
logger.error(f"Ошибка регистрации: {e}")
return {"success": False, "token": None, "author": None, "error": str(e)}
2022-09-03 10:50:14 +00:00
2021-08-01 11:40:24 +00:00
@mutation.field("sendLink")
async def send_link(
_: None, _info: GraphQLResolveInfo, email: str, lang: str = "ru", template: str = "confirm"
) -> dict[str, Any]:
2025-07-02 21:20:10 +00:00
"""Отправляет ссылку подтверждения"""
try:
result = await auth_service.send_verification_link(email, lang, template)
return result
except Exception as e:
raise handle_error("отправке ссылки подтверждения", e) from e
2025-07-02 19:30:21 +00:00
2025-07-02 21:20:10 +00:00
@mutation.field("confirmEmail")
@auth_service.login_required
async def confirm_email(_: None, _info: GraphQLResolveInfo, token: str) -> dict[str, Any]:
"""Подтверждает email по токену"""
try:
return await auth_service.confirm_email(token)
except Exception as e:
logger.error(f"Ошибка подтверждения email: {e}")
return {"success": False, "token": None, "author": None, "error": str(e)}
2025-05-16 06:23:48 +00:00
2025-07-02 21:20:10 +00:00
@mutation.field("login")
async def login(_: None, info: GraphQLResolveInfo, **kwargs: Any) -> dict[str, Any]:
"""Авторизация пользователя"""
try:
email = kwargs.get("email", "")
password = kwargs.get("password", "")
request = info.context.get("request")
2025-05-16 06:23:48 +00:00
2025-07-02 21:20:10 +00:00
result = await auth_service.login(email, password, request)
2025-05-16 06:23:48 +00:00
2025-07-02 21:20:10 +00:00
# Устанавливаем cookie если есть токен
if result.get("success") and result.get("token") and request:
2025-05-16 06:23:48 +00:00
try:
2025-07-02 21:20:10 +00:00
from starlette.responses import JSONResponse
if not hasattr(info.context, "response"):
response = JSONResponse({})
response.set_cookie(
key=SESSION_COOKIE_NAME,
value=result["token"],
httponly=True,
secure=True,
samesite="strict",
max_age=86400 * 30,
)
info.context["response"] = response
except Exception as cookie_error:
logger.warning(f"Не удалось установить cookie: {cookie_error}")
2025-05-16 06:23:48 +00:00
2025-07-02 19:30:21 +00:00
return result
2025-07-02 21:20:10 +00:00
except Exception as e:
logger.error(f"Ошибка входа: {e}")
return {"success": False, "token": None, "author": None, "error": str(e)}
2025-05-16 06:23:48 +00:00
2025-05-19 08:25:41 +00:00
@mutation.field("logout")
2025-07-02 21:20:10 +00:00
@auth_service.login_required
async def logout(_: None, info: GraphQLResolveInfo, **kwargs: Any) -> dict[str, Any]:
"""Выход из системы"""
try:
author = info.context.get("author")
if not author:
return {"success": False, "message": "Пользователь не найден в контексте"}
user_id = str(author.get("id"))
request = info.context.get("request")
2025-07-02 21:20:10 +00:00
# Получаем токен
token = None
if request:
token = request.cookies.get(SESSION_COOKIE_NAME)
if not token:
auth_header = request.headers.get("Authorization")
if auth_header and auth_header.startswith("Bearer "):
2025-07-02 21:20:10 +00:00
token = auth_header[7:]
2025-05-19 08:25:41 +00:00
2025-07-02 21:20:10 +00:00
result = await auth_service.logout(user_id, token)
2025-05-16 06:23:48 +00:00
2025-07-02 21:20:10 +00:00
# Удаляем cookie
if request and hasattr(info.context, "response"):
try:
info.context["response"].delete_cookie(SESSION_COOKIE_NAME)
except Exception as e:
logger.warning(f"Не удалось удалить cookie: {e}")
return result
except Exception as e:
logger.error(f"Ошибка выхода: {e}")
return {"success": False, "message": str(e)}
2025-05-16 06:23:48 +00:00
2025-05-19 08:25:41 +00:00
@mutation.field("refreshToken")
2025-07-02 21:20:10 +00:00
@auth_service.login_required
async def refresh_token(_: None, info: GraphQLResolveInfo, **kwargs: Any) -> dict[str, Any]:
"""Обновление токена"""
try:
author = info.context.get("author")
if not author:
2025-07-02 21:20:10 +00:00
return {"success": False, "token": None, "author": None, "error": "Пользователь не найден"}
2025-07-02 21:20:10 +00:00
user_id = str(author.get("id"))
request = info.context.get("request")
2025-07-02 21:20:10 +00:00
if not request:
2025-07-02 21:20:10 +00:00
return {"success": False, "token": None, "author": None, "error": "Запрос не найден"}
2025-07-02 21:20:10 +00:00
# Получаем токен
token = request.cookies.get(SESSION_COOKIE_NAME)
if not token:
auth_header = request.headers.get("Authorization")
if auth_header and auth_header.startswith("Bearer "):
2025-07-02 21:20:10 +00:00
token = auth_header[7:]
if not token:
return {"success": False, "token": None, "author": None, "error": "Токен не найден"}
device_info = {
"ip": request.client.host if request.client else "unknown",
"user_agent": request.headers.get("user-agent"),
}
2025-07-02 21:20:10 +00:00
result = await auth_service.refresh_token(user_id, token, device_info)
# Устанавливаем новый cookie
if result.get("success") and result.get("token"):
try:
if hasattr(info.context, "response"):
info.context["response"].set_cookie(
key=SESSION_COOKIE_NAME,
value=result["token"],
httponly=True,
secure=True,
samesite="strict",
max_age=86400 * 30,
)
except Exception as e:
logger.warning(f"Не удалось обновить cookie: {e}")
2025-05-19 08:25:41 +00:00
2025-07-02 21:20:10 +00:00
return result
except Exception as e:
2025-07-02 21:20:10 +00:00
logger.error(f"Ошибка обновления токена: {e}")
return {"success": False, "token": None, "author": None, "error": str(e)}
@mutation.field("requestPasswordReset")
async def request_password_reset(_: None, _info: GraphQLResolveInfo, **kwargs: Any) -> dict[str, Any]:
"""Запрос сброса пароля"""
2025-05-16 06:23:48 +00:00
try:
2025-07-02 21:20:10 +00:00
email = kwargs.get("email", "")
lang = kwargs.get("lang", "ru")
return await auth_service.request_password_reset(email, lang)
except Exception as e:
2025-07-02 21:20:10 +00:00
logger.error(f"Ошибка запроса сброса пароля: {e}")
return {"success": False}
@mutation.field("updateSecurity")
2025-07-02 21:20:10 +00:00
@auth_service.login_required
async def update_security(_: None, info: GraphQLResolveInfo, **kwargs: Any) -> dict[str, Any]:
"""Обновление пароля и email"""
try:
2025-07-02 21:20:10 +00:00
author = info.context.get("author")
if not author:
return {"success": False, "error": "NOT_AUTHENTICATED", "author": None}
user_id = author.get("id")
old_password = kwargs.get("oldPassword", "")
new_password = kwargs.get("newPassword")
email = kwargs.get("email")
2025-07-02 21:20:10 +00:00
return await auth_service.update_security(user_id, old_password, new_password, email)
except Exception as e:
2025-07-02 21:20:10 +00:00
logger.error(f"Ошибка обновления безопасности: {e}")
return {"success": False, "error": str(e), "author": None}
@mutation.field("confirmEmailChange")
2025-07-02 21:20:10 +00:00
@auth_service.login_required
async def confirm_email_change(_: None, info: GraphQLResolveInfo, **kwargs: Any) -> dict[str, Any]:
2025-07-02 21:20:10 +00:00
"""Подтверждение смены email по токену"""
try:
2025-07-02 21:20:10 +00:00
author = info.context.get("author")
if not author:
return {"success": False, "error": "NOT_AUTHENTICATED", "author": None}
2025-05-16 06:23:48 +00:00
2025-07-02 21:20:10 +00:00
user_id = author.get("id")
token = kwargs.get("token", "")
return await auth_service.confirm_email_change(user_id, token)
2025-05-16 06:23:48 +00:00
except Exception as e:
2025-07-02 21:20:10 +00:00
logger.error(f"Ошибка подтверждения смены email: {e}")
return {"success": False, "error": str(e), "author": None}
@mutation.field("cancelEmailChange")
2025-07-02 21:20:10 +00:00
@auth_service.login_required
async def cancel_email_change(_: None, info: GraphQLResolveInfo, **kwargs: Any) -> dict[str, Any]:
"""Отмена смены email"""
try:
2025-07-02 21:20:10 +00:00
author = info.context.get("author")
if not author:
return {"success": False, "error": "NOT_AUTHENTICATED", "author": None}
2025-07-02 21:20:10 +00:00
user_id = author.get("id")
return await auth_service.cancel_email_change(user_id)
except Exception as e:
logger.error(f"Ошибка отмены смены email: {e}")
return {"success": False, "error": str(e), "author": None}
2025-07-02 21:20:10 +00:00
@mutation.field("getSession")
@auth_service.login_required
async def get_session(_: None, info: GraphQLResolveInfo, **kwargs: Any) -> dict[str, Any]:
"""Получает информацию о текущей сессии"""
try:
# Получаем токен из контекста (установлен декоратором login_required)
token = info.context.get("token")
author = info.context.get("author")
2025-07-02 21:20:10 +00:00
if not token:
return {"success": False, "token": None, "author": None, "error": "Токен не найден"}
2025-07-02 21:20:10 +00:00
if not author:
return {"success": False, "token": None, "author": None, "error": "Пользователь не найден"}
2025-07-02 21:20:10 +00:00
return {"success": True, "token": token, "author": author, "error": None}
except Exception as e:
2025-07-02 21:20:10 +00:00
logger.error(f"Ошибка получения сессии: {e}")
return {"success": False, "token": None, "author": None, "error": str(e)}
2025-07-02 19:30:21 +00:00
2025-07-02 21:20:10 +00:00
# === ЗАПРОСЫ ===
2025-07-02 19:30:21 +00:00
2025-07-02 21:20:10 +00:00
@query.field("isEmailUsed")
async def is_email_used(_: None, _info: GraphQLResolveInfo, email: str) -> bool:
"""Проверяет, используется ли email"""
try:
return auth_service.is_email_used(email)
except Exception as e:
logger.error(f"Ошибка проверки email: {e}")
return False