2025-07-03 00:20:10 +03:00
|
|
|
|
"""
|
|
|
|
|
|
Auth резолверы - тонкие GraphQL обёртки над AuthService
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
2025-08-17 16:33:54 +03:00
|
|
|
|
from typing import Any
|
2023-10-27 00:07:35 +03:00
|
|
|
|
|
2025-06-02 02:56:11 +03:00
|
|
|
|
from graphql import GraphQLResolveInfo
|
2025-07-31 18:55:59 +03:00
|
|
|
|
from starlette.responses import JSONResponse
|
2023-10-30 22:00:55 +01:00
|
|
|
|
|
[0.9.7] - 2025-08-18
### 🔄 Изменения
- **SQLAlchemy KeyError** - исправление ошибки `KeyError: Reaction` при инициализации
- **Исправлена ошибка SQLAlchemy**: Устранена проблема `InvalidRequestError: When initializing mapper Mapper[Shout(shout)], expression Reaction failed to locate a name (Reaction)`
### 🧪 Тестирование
- **Исправление тестов** - адаптация к новой структуре моделей
- **RBAC инициализация** - добавление `rbac.initialize_rbac()` в `conftest.py`
- **Создан тест для getSession**: Добавлен комплексный тест `test_getSession_cookies.py` с проверкой всех сценариев
- **Покрытие edge cases**: Тесты проверяют работу с валидными/невалидными токенами, отсутствующими пользователями
- **Мокирование зависимостей**: Использование unittest.mock для изоляции тестируемого кода
### 🔧 Рефакторинг
- **Упрощена архитектура**: Убраны сложные конструкции с отложенными импортами, заменены на чистую архитектуру
- **Перемещение моделей** - `Author` и связанные модели перенесены в `orm/author.py`: Вынесены базовые модели пользователей (`Author`, `AuthorFollower`, `AuthorBookmark`, `AuthorRating`) из `orm.author` в отдельный модуль
- **Устранены циклические импорты**: Разорван цикл между `auth.core` → `orm.community` → `orm.author` через реструктуризацию архитектуры
- **Создан модуль `utils/password.py`**: Класс `Password` вынесен в utils для избежания циклических зависимостей
- **Оптимизированы импорты моделей**: Убран прямой импорт `Shout` из `orm/community.py`, заменен на строковые ссылки
### 🔧 Авторизация с cookies
- **getSession теперь работает с cookies**: Мутация `getSession` теперь может получать токен из httpOnly cookies даже без заголовка Authorization
- **Убрано требование авторизации**: `getSession` больше не требует декоратор `@login_required`, работает автономно
- **Поддержка dual-авторизации**: Токен может быть получен как из заголовка Authorization, так и из cookie `session_token`
- **Автоматическая установка cookies**: Middleware автоматически устанавливает httpOnly cookies при успешном `getSession`
- **Обновлена GraphQL схема**: `SessionInfo` теперь содержит поля `success`, `error` и опциональные `token`, `author`
- **Единообразная обработка токенов**: Все модули теперь используют централизованные функции для работы с токенами
- **Улучшена обработка ошибок**: Добавлена детальная валидация токенов и пользователей в `getSession`
- **Логирование операций**: Добавлены подробные логи для отслеживания процесса авторизации
### 📝 Документация
- **Обновлена схема GraphQL**: `SessionInfo` тип теперь соответствует новому формату ответа
- Обновлена документация RBAC
- Обновлена документация авторизации с cookies
2025-08-18 14:25:25 +03:00
|
|
|
|
from auth.utils import extract_token_from_request, get_auth_token_from_context, get_user_data_by_token
|
2025-07-03 00:20:10 +03:00
|
|
|
|
from services.auth import auth_service
|
|
|
|
|
|
from settings import SESSION_COOKIE_NAME
|
[0.9.7] - 2025-08-18
### 🔄 Изменения
- **SQLAlchemy KeyError** - исправление ошибки `KeyError: Reaction` при инициализации
- **Исправлена ошибка SQLAlchemy**: Устранена проблема `InvalidRequestError: When initializing mapper Mapper[Shout(shout)], expression Reaction failed to locate a name (Reaction)`
### 🧪 Тестирование
- **Исправление тестов** - адаптация к новой структуре моделей
- **RBAC инициализация** - добавление `rbac.initialize_rbac()` в `conftest.py`
- **Создан тест для getSession**: Добавлен комплексный тест `test_getSession_cookies.py` с проверкой всех сценариев
- **Покрытие edge cases**: Тесты проверяют работу с валидными/невалидными токенами, отсутствующими пользователями
- **Мокирование зависимостей**: Использование unittest.mock для изоляции тестируемого кода
### 🔧 Рефакторинг
- **Упрощена архитектура**: Убраны сложные конструкции с отложенными импортами, заменены на чистую архитектуру
- **Перемещение моделей** - `Author` и связанные модели перенесены в `orm/author.py`: Вынесены базовые модели пользователей (`Author`, `AuthorFollower`, `AuthorBookmark`, `AuthorRating`) из `orm.author` в отдельный модуль
- **Устранены циклические импорты**: Разорван цикл между `auth.core` → `orm.community` → `orm.author` через реструктуризацию архитектуры
- **Создан модуль `utils/password.py`**: Класс `Password` вынесен в utils для избежания циклических зависимостей
- **Оптимизированы импорты моделей**: Убран прямой импорт `Shout` из `orm/community.py`, заменен на строковые ссылки
### 🔧 Авторизация с cookies
- **getSession теперь работает с cookies**: Мутация `getSession` теперь может получать токен из httpOnly cookies даже без заголовка Authorization
- **Убрано требование авторизации**: `getSession` больше не требует декоратор `@login_required`, работает автономно
- **Поддержка dual-авторизации**: Токен может быть получен как из заголовка Authorization, так и из cookie `session_token`
- **Автоматическая установка cookies**: Middleware автоматически устанавливает httpOnly cookies при успешном `getSession`
- **Обновлена GraphQL схема**: `SessionInfo` теперь содержит поля `success`, `error` и опциональные `token`, `author`
- **Единообразная обработка токенов**: Все модули теперь используют централизованные функции для работы с токенами
- **Улучшена обработка ошибок**: Добавлена детальная валидация токенов и пользователей в `getSession`
- **Логирование операций**: Добавлены подробные логи для отслеживания процесса авторизации
### 📝 Документация
- **Обновлена схема GraphQL**: `SessionInfo` тип теперь соответствует новому формату ответа
- Обновлена документация RBAC
- Обновлена документация авторизации с cookies
2025-08-18 14:25:25 +03:00
|
|
|
|
from storage.schema import mutation, query, type_author
|
2025-05-29 12:37:39 +03:00
|
|
|
|
from utils.logger import root_logger as logger
|
|
|
|
|
|
|
2025-07-03 00:20:10 +03:00
|
|
|
|
# === РЕЗОЛВЕР ДЛЯ ТИПА AUTHOR ===
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@type_author.field("roles")
|
2025-08-17 16:33:54 +03:00
|
|
|
|
def resolve_roles(obj: dict | Any, info: GraphQLResolveInfo) -> list[str]:
|
2025-07-03 00:20:10 +03:00
|
|
|
|
"""Резолвер для поля roles автора"""
|
2025-07-02 22:30:21 +03: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-03 00:20:10 +03:00
|
|
|
|
logger.error(f"Ошибка получения ролей: {e}")
|
2025-07-02 22:30:21 +03:00
|
|
|
|
return []
|
|
|
|
|
|
|
2022-09-03 13:50:14 +03:00
|
|
|
|
|
2025-07-03 00:20:10 +03:00
|
|
|
|
# === МУТАЦИИ АУТЕНТИФИКАЦИИ ===
|
2021-07-30 16:22:37 +03:00
|
|
|
|
|
2021-07-30 15:53:22 +03:00
|
|
|
|
|
2021-06-29 13:26:46 +03:00
|
|
|
|
@mutation.field("registerUser")
|
2025-07-03 00:20:10 +03:00
|
|
|
|
async def register_user(
|
|
|
|
|
|
_: None, _info: GraphQLResolveInfo, email: str, password: str = "", name: str = ""
|
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
|
"""Регистрирует нового пользователя"""
|
2025-05-16 09:23:48 +03:00
|
|
|
|
try:
|
2025-07-03 00:20:10 +03:00
|
|
|
|
return await auth_service.register_user(email, password, name)
|
2025-05-16 09:23:48 +03:00
|
|
|
|
except Exception as e:
|
2025-07-03 00:20:10 +03:00
|
|
|
|
logger.error(f"Ошибка регистрации: {e}")
|
|
|
|
|
|
return {"success": False, "token": None, "author": None, "error": str(e)}
|
2022-09-03 13:50:14 +03:00
|
|
|
|
|
2021-08-01 11:40:24 +00:00
|
|
|
|
|
2022-09-17 21:12:14 +03:00
|
|
|
|
@mutation.field("sendLink")
|
2025-06-02 02:56:11 +03:00
|
|
|
|
async def send_link(
|
|
|
|
|
|
_: None, _info: GraphQLResolveInfo, email: str, lang: str = "ru", template: str = "confirm"
|
2025-07-31 18:55:59 +03:00
|
|
|
|
) -> bool:
|
2025-07-03 00:20:10 +03:00
|
|
|
|
"""Отправляет ссылку подтверждения"""
|
|
|
|
|
|
try:
|
2025-07-31 18:55:59 +03:00
|
|
|
|
return bool(await auth_service.send_verification_link(email, lang, template))
|
2025-07-03 00:20:10 +03:00
|
|
|
|
except Exception as e:
|
2025-07-31 18:55:59 +03:00
|
|
|
|
logger.error(f"Ошибка отправки ссылки подтверждения: {e}")
|
|
|
|
|
|
return False
|
2025-07-02 22:30:21 +03:00
|
|
|
|
|
|
|
|
|
|
|
2025-07-03 00:20:10 +03: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)}
|
2022-09-17 21:12:14 +03:00
|
|
|
|
|
2025-05-16 09:23:48 +03:00
|
|
|
|
|
2025-07-03 00:20:10 +03: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 09:23:48 +03:00
|
|
|
|
|
2025-07-03 00:20:10 +03:00
|
|
|
|
result = await auth_service.login(email, password, request)
|
2025-05-16 09:23:48 +03:00
|
|
|
|
|
2025-07-03 00:20:10 +03:00
|
|
|
|
# Устанавливаем cookie если есть токен
|
|
|
|
|
|
if result.get("success") and result.get("token") and request:
|
2025-05-16 09:23:48 +03:00
|
|
|
|
try:
|
2025-07-03 00:20:10 +03:00
|
|
|
|
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 09:23:48 +03:00
|
|
|
|
|
2025-07-02 22:30:21 +03:00
|
|
|
|
return result
|
2025-07-03 00:20:10 +03:00
|
|
|
|
except Exception as e:
|
2025-08-27 18:31:51 +03:00
|
|
|
|
logger.warning(f"Ошибка входа: {e}")
|
2025-07-03 00:20:10 +03:00
|
|
|
|
return {"success": False, "token": None, "author": None, "error": str(e)}
|
2025-05-16 09:23:48 +03:00
|
|
|
|
|
|
|
|
|
|
|
2025-05-19 11:25:41 +03:00
|
|
|
|
@mutation.field("logout")
|
2025-07-03 00:20:10 +03:00
|
|
|
|
@auth_service.login_required
|
|
|
|
|
|
async def logout(_: None, info: GraphQLResolveInfo, **kwargs: Any) -> dict[str, Any]:
|
|
|
|
|
|
"""Выход из системы"""
|
2025-06-02 02:56:11 +03:00
|
|
|
|
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-03 00:20:10 +03:00
|
|
|
|
# Получаем токен
|
|
|
|
|
|
token = None
|
2025-06-02 02:56:11 +03:00
|
|
|
|
if request:
|
[0.9.7] - 2025-08-18
### 🔄 Изменения
- **SQLAlchemy KeyError** - исправление ошибки `KeyError: Reaction` при инициализации
- **Исправлена ошибка SQLAlchemy**: Устранена проблема `InvalidRequestError: When initializing mapper Mapper[Shout(shout)], expression Reaction failed to locate a name (Reaction)`
### 🧪 Тестирование
- **Исправление тестов** - адаптация к новой структуре моделей
- **RBAC инициализация** - добавление `rbac.initialize_rbac()` в `conftest.py`
- **Создан тест для getSession**: Добавлен комплексный тест `test_getSession_cookies.py` с проверкой всех сценариев
- **Покрытие edge cases**: Тесты проверяют работу с валидными/невалидными токенами, отсутствующими пользователями
- **Мокирование зависимостей**: Использование unittest.mock для изоляции тестируемого кода
### 🔧 Рефакторинг
- **Упрощена архитектура**: Убраны сложные конструкции с отложенными импортами, заменены на чистую архитектуру
- **Перемещение моделей** - `Author` и связанные модели перенесены в `orm/author.py`: Вынесены базовые модели пользователей (`Author`, `AuthorFollower`, `AuthorBookmark`, `AuthorRating`) из `orm.author` в отдельный модуль
- **Устранены циклические импорты**: Разорван цикл между `auth.core` → `orm.community` → `orm.author` через реструктуризацию архитектуры
- **Создан модуль `utils/password.py`**: Класс `Password` вынесен в utils для избежания циклических зависимостей
- **Оптимизированы импорты моделей**: Убран прямой импорт `Shout` из `orm/community.py`, заменен на строковые ссылки
### 🔧 Авторизация с cookies
- **getSession теперь работает с cookies**: Мутация `getSession` теперь может получать токен из httpOnly cookies даже без заголовка Authorization
- **Убрано требование авторизации**: `getSession` больше не требует декоратор `@login_required`, работает автономно
- **Поддержка dual-авторизации**: Токен может быть получен как из заголовка Authorization, так и из cookie `session_token`
- **Автоматическая установка cookies**: Middleware автоматически устанавливает httpOnly cookies при успешном `getSession`
- **Обновлена GraphQL схема**: `SessionInfo` теперь содержит поля `success`, `error` и опциональные `token`, `author`
- **Единообразная обработка токенов**: Все модули теперь используют централизованные функции для работы с токенами
- **Улучшена обработка ошибок**: Добавлена детальная валидация токенов и пользователей в `getSession`
- **Логирование операций**: Добавлены подробные логи для отслеживания процесса авторизации
### 📝 Документация
- **Обновлена схема GraphQL**: `SessionInfo` тип теперь соответствует новому формату ответа
- Обновлена документация RBAC
- Обновлена документация авторизации с cookies
2025-08-18 14:25:25 +03:00
|
|
|
|
token = await extract_token_from_request(request)
|
2025-05-19 11:25:41 +03:00
|
|
|
|
|
2025-07-03 00:20:10 +03:00
|
|
|
|
result = await auth_service.logout(user_id, token)
|
2025-05-16 09:23:48 +03:00
|
|
|
|
|
2025-07-03 00:20:10 +03: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:
|
2025-08-27 18:31:51 +03:00
|
|
|
|
logger.warning(f"Ошибка выхода: {e}")
|
2025-07-25 09:46:52 +03:00
|
|
|
|
return {"success": False}
|
2025-05-16 09:23:48 +03:00
|
|
|
|
|
|
|
|
|
|
|
2025-05-19 11:25:41 +03:00
|
|
|
|
@mutation.field("refreshToken")
|
2025-07-03 00:20:10 +03:00
|
|
|
|
@auth_service.login_required
|
|
|
|
|
|
async def refresh_token(_: None, info: GraphQLResolveInfo, **kwargs: Any) -> dict[str, Any]:
|
|
|
|
|
|
"""Обновление токена"""
|
2025-06-02 02:56:11 +03:00
|
|
|
|
try:
|
|
|
|
|
|
author = info.context.get("author")
|
|
|
|
|
|
if not author:
|
2025-07-03 00:20:10 +03:00
|
|
|
|
return {"success": False, "token": None, "author": None, "error": "Пользователь не найден"}
|
2025-06-02 02:56:11 +03:00
|
|
|
|
|
2025-07-03 00:20:10 +03:00
|
|
|
|
user_id = str(author.get("id"))
|
2025-06-02 02:56:11 +03:00
|
|
|
|
request = info.context.get("request")
|
2025-07-03 00:20:10 +03:00
|
|
|
|
|
2025-06-02 02:56:11 +03:00
|
|
|
|
if not request:
|
2025-07-03 00:20:10 +03:00
|
|
|
|
return {"success": False, "token": None, "author": None, "error": "Запрос не найден"}
|
2025-06-02 02:56:11 +03:00
|
|
|
|
|
2025-07-03 00:20:10 +03:00
|
|
|
|
# Получаем токен
|
[0.9.7] - 2025-08-18
### 🔄 Изменения
- **SQLAlchemy KeyError** - исправление ошибки `KeyError: Reaction` при инициализации
- **Исправлена ошибка SQLAlchemy**: Устранена проблема `InvalidRequestError: When initializing mapper Mapper[Shout(shout)], expression Reaction failed to locate a name (Reaction)`
### 🧪 Тестирование
- **Исправление тестов** - адаптация к новой структуре моделей
- **RBAC инициализация** - добавление `rbac.initialize_rbac()` в `conftest.py`
- **Создан тест для getSession**: Добавлен комплексный тест `test_getSession_cookies.py` с проверкой всех сценариев
- **Покрытие edge cases**: Тесты проверяют работу с валидными/невалидными токенами, отсутствующими пользователями
- **Мокирование зависимостей**: Использование unittest.mock для изоляции тестируемого кода
### 🔧 Рефакторинг
- **Упрощена архитектура**: Убраны сложные конструкции с отложенными импортами, заменены на чистую архитектуру
- **Перемещение моделей** - `Author` и связанные модели перенесены в `orm/author.py`: Вынесены базовые модели пользователей (`Author`, `AuthorFollower`, `AuthorBookmark`, `AuthorRating`) из `orm.author` в отдельный модуль
- **Устранены циклические импорты**: Разорван цикл между `auth.core` → `orm.community` → `orm.author` через реструктуризацию архитектуры
- **Создан модуль `utils/password.py`**: Класс `Password` вынесен в utils для избежания циклических зависимостей
- **Оптимизированы импорты моделей**: Убран прямой импорт `Shout` из `orm/community.py`, заменен на строковые ссылки
### 🔧 Авторизация с cookies
- **getSession теперь работает с cookies**: Мутация `getSession` теперь может получать токен из httpOnly cookies даже без заголовка Authorization
- **Убрано требование авторизации**: `getSession` больше не требует декоратор `@login_required`, работает автономно
- **Поддержка dual-авторизации**: Токен может быть получен как из заголовка Authorization, так и из cookie `session_token`
- **Автоматическая установка cookies**: Middleware автоматически устанавливает httpOnly cookies при успешном `getSession`
- **Обновлена GraphQL схема**: `SessionInfo` теперь содержит поля `success`, `error` и опциональные `token`, `author`
- **Единообразная обработка токенов**: Все модули теперь используют централизованные функции для работы с токенами
- **Улучшена обработка ошибок**: Добавлена детальная валидация токенов и пользователей в `getSession`
- **Логирование операций**: Добавлены подробные логи для отслеживания процесса авторизации
### 📝 Документация
- **Обновлена схема GraphQL**: `SessionInfo` тип теперь соответствует новому формату ответа
- Обновлена документация RBAC
- Обновлена документация авторизации с cookies
2025-08-18 14:25:25 +03:00
|
|
|
|
token = await extract_token_from_request(request)
|
2025-06-02 02:56:11 +03:00
|
|
|
|
|
|
|
|
|
|
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-03 00:20:10 +03: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 11:25:41 +03:00
|
|
|
|
|
2025-07-03 00:20:10 +03:00
|
|
|
|
return result
|
2025-06-02 02:56:11 +03:00
|
|
|
|
except Exception as e:
|
2025-08-27 18:31:51 +03:00
|
|
|
|
logger.warning(f"Ошибка обновления токена: {e}")
|
2025-06-02 02:56:11 +03:00
|
|
|
|
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 09:23:48 +03:00
|
|
|
|
try:
|
2025-07-03 00:20:10 +03:00
|
|
|
|
email = kwargs.get("email", "")
|
|
|
|
|
|
lang = kwargs.get("lang", "ru")
|
|
|
|
|
|
return await auth_service.request_password_reset(email, lang)
|
2025-06-02 02:56:11 +03:00
|
|
|
|
except Exception as e:
|
2025-07-03 00:20:10 +03:00
|
|
|
|
logger.error(f"Ошибка запроса сброса пароля: {e}")
|
2025-06-02 02:56:11 +03:00
|
|
|
|
return {"success": False}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@mutation.field("updateSecurity")
|
2025-07-03 00:20:10 +03:00
|
|
|
|
@auth_service.login_required
|
|
|
|
|
|
async def update_security(_: None, info: GraphQLResolveInfo, **kwargs: Any) -> dict[str, Any]:
|
|
|
|
|
|
"""Обновление пароля и email"""
|
2025-06-02 02:56:11 +03:00
|
|
|
|
try:
|
2025-07-03 00:20:10 +03: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-06-02 02:56:11 +03:00
|
|
|
|
|
2025-07-03 00:20:10 +03:00
|
|
|
|
return await auth_service.update_security(user_id, old_password, new_password, email)
|
2025-06-02 02:56:11 +03:00
|
|
|
|
except Exception as e:
|
2025-07-03 00:20:10 +03:00
|
|
|
|
logger.error(f"Ошибка обновления безопасности: {e}")
|
2025-06-02 02:56:11 +03:00
|
|
|
|
return {"success": False, "error": str(e), "author": None}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@mutation.field("confirmEmailChange")
|
2025-07-03 00:20:10 +03:00
|
|
|
|
@auth_service.login_required
|
2025-06-02 02:56:11 +03:00
|
|
|
|
async def confirm_email_change(_: None, info: GraphQLResolveInfo, **kwargs: Any) -> dict[str, Any]:
|
2025-07-03 00:20:10 +03:00
|
|
|
|
"""Подтверждение смены email по токену"""
|
2025-06-02 02:56:11 +03:00
|
|
|
|
try:
|
2025-07-03 00:20:10 +03:00
|
|
|
|
author = info.context.get("author")
|
|
|
|
|
|
if not author:
|
|
|
|
|
|
return {"success": False, "error": "NOT_AUTHENTICATED", "author": None}
|
2025-05-16 09:23:48 +03:00
|
|
|
|
|
2025-07-03 00:20:10 +03:00
|
|
|
|
user_id = author.get("id")
|
|
|
|
|
|
token = kwargs.get("token", "")
|
|
|
|
|
|
|
|
|
|
|
|
return await auth_service.confirm_email_change(user_id, token)
|
2025-05-16 09:23:48 +03:00
|
|
|
|
except Exception as e:
|
2025-07-03 00:20:10 +03:00
|
|
|
|
logger.error(f"Ошибка подтверждения смены email: {e}")
|
2025-06-02 02:56:11 +03:00
|
|
|
|
return {"success": False, "error": str(e), "author": None}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@mutation.field("cancelEmailChange")
|
2025-07-03 00:20:10 +03:00
|
|
|
|
@auth_service.login_required
|
|
|
|
|
|
async def cancel_email_change(_: None, info: GraphQLResolveInfo, **kwargs: Any) -> dict[str, Any]:
|
|
|
|
|
|
"""Отмена смены email"""
|
2025-06-02 02:56:11 +03:00
|
|
|
|
try:
|
2025-07-03 00:20:10 +03:00
|
|
|
|
author = info.context.get("author")
|
|
|
|
|
|
if not author:
|
|
|
|
|
|
return {"success": False, "error": "NOT_AUTHENTICATED", "author": None}
|
2025-06-02 02:56:11 +03:00
|
|
|
|
|
2025-07-03 00:20:10 +03: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-06-02 02:56:11 +03:00
|
|
|
|
|
|
|
|
|
|
|
2025-07-03 00:20:10 +03:00
|
|
|
|
@mutation.field("getSession")
|
|
|
|
|
|
async def get_session(_: None, info: GraphQLResolveInfo, **kwargs: Any) -> dict[str, Any]:
|
|
|
|
|
|
"""Получает информацию о текущей сессии"""
|
|
|
|
|
|
try:
|
[0.9.7] - 2025-08-18
### 🔄 Изменения
- **SQLAlchemy KeyError** - исправление ошибки `KeyError: Reaction` при инициализации
- **Исправлена ошибка SQLAlchemy**: Устранена проблема `InvalidRequestError: When initializing mapper Mapper[Shout(shout)], expression Reaction failed to locate a name (Reaction)`
### 🧪 Тестирование
- **Исправление тестов** - адаптация к новой структуре моделей
- **RBAC инициализация** - добавление `rbac.initialize_rbac()` в `conftest.py`
- **Создан тест для getSession**: Добавлен комплексный тест `test_getSession_cookies.py` с проверкой всех сценариев
- **Покрытие edge cases**: Тесты проверяют работу с валидными/невалидными токенами, отсутствующими пользователями
- **Мокирование зависимостей**: Использование unittest.mock для изоляции тестируемого кода
### 🔧 Рефакторинг
- **Упрощена архитектура**: Убраны сложные конструкции с отложенными импортами, заменены на чистую архитектуру
- **Перемещение моделей** - `Author` и связанные модели перенесены в `orm/author.py`: Вынесены базовые модели пользователей (`Author`, `AuthorFollower`, `AuthorBookmark`, `AuthorRating`) из `orm.author` в отдельный модуль
- **Устранены циклические импорты**: Разорван цикл между `auth.core` → `orm.community` → `orm.author` через реструктуризацию архитектуры
- **Создан модуль `utils/password.py`**: Класс `Password` вынесен в utils для избежания циклических зависимостей
- **Оптимизированы импорты моделей**: Убран прямой импорт `Shout` из `orm/community.py`, заменен на строковые ссылки
### 🔧 Авторизация с cookies
- **getSession теперь работает с cookies**: Мутация `getSession` теперь может получать токен из httpOnly cookies даже без заголовка Authorization
- **Убрано требование авторизации**: `getSession` больше не требует декоратор `@login_required`, работает автономно
- **Поддержка dual-авторизации**: Токен может быть получен как из заголовка Authorization, так и из cookie `session_token`
- **Автоматическая установка cookies**: Middleware автоматически устанавливает httpOnly cookies при успешном `getSession`
- **Обновлена GraphQL схема**: `SessionInfo` теперь содержит поля `success`, `error` и опциональные `token`, `author`
- **Единообразная обработка токенов**: Все модули теперь используют централизованные функции для работы с токенами
- **Улучшена обработка ошибок**: Добавлена детальная валидация токенов и пользователей в `getSession`
- **Логирование операций**: Добавлены подробные логи для отслеживания процесса авторизации
### 📝 Документация
- **Обновлена схема GraphQL**: `SessionInfo` тип теперь соответствует новому формату ответа
- Обновлена документация RBAC
- Обновлена документация авторизации с cookies
2025-08-18 14:25:25 +03:00
|
|
|
|
token = await get_auth_token_from_context(info)
|
2025-06-02 02:56:11 +03:00
|
|
|
|
|
2025-07-03 00:20:10 +03:00
|
|
|
|
if not token:
|
[0.9.7] - 2025-08-18
### 🔄 Изменения
- **SQLAlchemy KeyError** - исправление ошибки `KeyError: Reaction` при инициализации
- **Исправлена ошибка SQLAlchemy**: Устранена проблема `InvalidRequestError: When initializing mapper Mapper[Shout(shout)], expression Reaction failed to locate a name (Reaction)`
### 🧪 Тестирование
- **Исправление тестов** - адаптация к новой структуре моделей
- **RBAC инициализация** - добавление `rbac.initialize_rbac()` в `conftest.py`
- **Создан тест для getSession**: Добавлен комплексный тест `test_getSession_cookies.py` с проверкой всех сценариев
- **Покрытие edge cases**: Тесты проверяют работу с валидными/невалидными токенами, отсутствующими пользователями
- **Мокирование зависимостей**: Использование unittest.mock для изоляции тестируемого кода
### 🔧 Рефакторинг
- **Упрощена архитектура**: Убраны сложные конструкции с отложенными импортами, заменены на чистую архитектуру
- **Перемещение моделей** - `Author` и связанные модели перенесены в `orm/author.py`: Вынесены базовые модели пользователей (`Author`, `AuthorFollower`, `AuthorBookmark`, `AuthorRating`) из `orm.author` в отдельный модуль
- **Устранены циклические импорты**: Разорван цикл между `auth.core` → `orm.community` → `orm.author` через реструктуризацию архитектуры
- **Создан модуль `utils/password.py`**: Класс `Password` вынесен в utils для избежания циклических зависимостей
- **Оптимизированы импорты моделей**: Убран прямой импорт `Shout` из `orm/community.py`, заменен на строковые ссылки
### 🔧 Авторизация с cookies
- **getSession теперь работает с cookies**: Мутация `getSession` теперь может получать токен из httpOnly cookies даже без заголовка Authorization
- **Убрано требование авторизации**: `getSession` больше не требует декоратор `@login_required`, работает автономно
- **Поддержка dual-авторизации**: Токен может быть получен как из заголовка Authorization, так и из cookie `session_token`
- **Автоматическая установка cookies**: Middleware автоматически устанавливает httpOnly cookies при успешном `getSession`
- **Обновлена GraphQL схема**: `SessionInfo` теперь содержит поля `success`, `error` и опциональные `token`, `author`
- **Единообразная обработка токенов**: Все модули теперь используют централизованные функции для работы с токенами
- **Улучшена обработка ошибок**: Добавлена детальная валидация токенов и пользователей в `getSession`
- **Логирование операций**: Добавлены подробные логи для отслеживания процесса авторизации
### 📝 Документация
- **Обновлена схема GraphQL**: `SessionInfo` тип теперь соответствует новому формату ответа
- Обновлена документация RBAC
- Обновлена документация авторизации с cookies
2025-08-18 14:25:25 +03:00
|
|
|
|
logger.debug("[getSession] Токен не найден")
|
|
|
|
|
|
return {"success": False, "token": None, "author": None, "error": "Сессия не найдена"}
|
2025-06-02 02:56:11 +03:00
|
|
|
|
|
[0.9.7] - 2025-08-18
### 🔄 Изменения
- **SQLAlchemy KeyError** - исправление ошибки `KeyError: Reaction` при инициализации
- **Исправлена ошибка SQLAlchemy**: Устранена проблема `InvalidRequestError: When initializing mapper Mapper[Shout(shout)], expression Reaction failed to locate a name (Reaction)`
### 🧪 Тестирование
- **Исправление тестов** - адаптация к новой структуре моделей
- **RBAC инициализация** - добавление `rbac.initialize_rbac()` в `conftest.py`
- **Создан тест для getSession**: Добавлен комплексный тест `test_getSession_cookies.py` с проверкой всех сценариев
- **Покрытие edge cases**: Тесты проверяют работу с валидными/невалидными токенами, отсутствующими пользователями
- **Мокирование зависимостей**: Использование unittest.mock для изоляции тестируемого кода
### 🔧 Рефакторинг
- **Упрощена архитектура**: Убраны сложные конструкции с отложенными импортами, заменены на чистую архитектуру
- **Перемещение моделей** - `Author` и связанные модели перенесены в `orm/author.py`: Вынесены базовые модели пользователей (`Author`, `AuthorFollower`, `AuthorBookmark`, `AuthorRating`) из `orm.author` в отдельный модуль
- **Устранены циклические импорты**: Разорван цикл между `auth.core` → `orm.community` → `orm.author` через реструктуризацию архитектуры
- **Создан модуль `utils/password.py`**: Класс `Password` вынесен в utils для избежания циклических зависимостей
- **Оптимизированы импорты моделей**: Убран прямой импорт `Shout` из `orm/community.py`, заменен на строковые ссылки
### 🔧 Авторизация с cookies
- **getSession теперь работает с cookies**: Мутация `getSession` теперь может получать токен из httpOnly cookies даже без заголовка Authorization
- **Убрано требование авторизации**: `getSession` больше не требует декоратор `@login_required`, работает автономно
- **Поддержка dual-авторизации**: Токен может быть получен как из заголовка Authorization, так и из cookie `session_token`
- **Автоматическая установка cookies**: Middleware автоматически устанавливает httpOnly cookies при успешном `getSession`
- **Обновлена GraphQL схема**: `SessionInfo` теперь содержит поля `success`, `error` и опциональные `token`, `author`
- **Единообразная обработка токенов**: Все модули теперь используют централизованные функции для работы с токенами
- **Улучшена обработка ошибок**: Добавлена детальная валидация токенов и пользователей в `getSession`
- **Логирование операций**: Добавлены подробные логи для отслеживания процесса авторизации
### 📝 Документация
- **Обновлена схема GraphQL**: `SessionInfo` тип теперь соответствует новому формату ответа
- Обновлена документация RBAC
- Обновлена документация авторизации с cookies
2025-08-18 14:25:25 +03:00
|
|
|
|
# Используем DRY функцию для получения данных пользователя
|
|
|
|
|
|
success, user_data, error_message = await get_user_data_by_token(token)
|
|
|
|
|
|
|
|
|
|
|
|
if success and user_data:
|
|
|
|
|
|
user_id = user_data.get("id", "NO_ID")
|
|
|
|
|
|
logger.debug(f"[getSession] Сессия валидна для пользователя {user_id}")
|
|
|
|
|
|
return {"success": True, "token": token, "author": user_data, "error": None}
|
|
|
|
|
|
logger.warning(f"[getSession] Ошибка валидации токена: {error_message}")
|
|
|
|
|
|
return {"success": False, "token": None, "author": None, "error": error_message}
|
2025-06-02 02:56:11 +03:00
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
2025-08-27 18:31:51 +03:00
|
|
|
|
logger.warning(f"Ошибка получения сессии: {e}")
|
2025-07-03 00:20:10 +03:00
|
|
|
|
return {"success": False, "token": None, "author": None, "error": str(e)}
|
2025-07-02 22:30:21 +03:00
|
|
|
|
|
|
|
|
|
|
|
2025-07-03 00:20:10 +03:00
|
|
|
|
# === ЗАПРОСЫ ===
|
2025-07-02 22:30:21 +03:00
|
|
|
|
|
|
|
|
|
|
|
2025-07-03 00:20:10 +03: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
|