2025-05-16 09:23:48 +03:00
|
|
|
|
"""
|
2025-05-30 14:05:50 +03:00
|
|
|
|
Единый middleware для обработки авторизации в GraphQL запросах
|
2025-05-16 09:23:48 +03:00
|
|
|
|
"""
|
2025-05-29 12:37:39 +03:00
|
|
|
|
|
2025-05-30 14:05:50 +03:00
|
|
|
|
import time
|
2025-06-02 02:56:11 +03:00
|
|
|
|
from collections.abc import Awaitable, MutableMapping
|
2025-08-17 16:33:54 +03:00
|
|
|
|
from typing import Any, Callable
|
2025-05-29 12:37:39 +03:00
|
|
|
|
|
2025-06-02 02:56:11 +03:00
|
|
|
|
from graphql import GraphQLResolveInfo
|
2025-05-30 14:08:29 +03:00
|
|
|
|
from sqlalchemy.orm import exc
|
2025-05-30 14:05:50 +03:00
|
|
|
|
from starlette.authentication import UnauthenticatedUser
|
2025-05-22 04:34:30 +03:00
|
|
|
|
from starlette.requests import Request
|
|
|
|
|
|
from starlette.responses import JSONResponse, Response
|
2025-06-02 02:56:11 +03:00
|
|
|
|
from starlette.types import ASGIApp
|
2025-05-29 12:37:39 +03:00
|
|
|
|
|
2025-05-30 14:05:50 +03:00
|
|
|
|
from auth.credentials import AuthCredentials
|
2025-06-02 21:50:58 +03:00
|
|
|
|
from auth.tokens.storage import TokenStorage as TokenManager
|
[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 orm.author import Author
|
2025-05-29 12:37:39 +03:00
|
|
|
|
from settings import (
|
2025-05-30 14:05:50 +03:00
|
|
|
|
ADMIN_EMAILS as ADMIN_EMAILS_LIST,
|
2025-05-30 14:08:29 +03:00
|
|
|
|
)
|
|
|
|
|
|
from settings import (
|
2025-09-28 12:22:37 +03:00
|
|
|
|
SESSION_COOKIE_DOMAIN,
|
2025-05-29 12:37:39 +03:00
|
|
|
|
SESSION_COOKIE_HTTPONLY,
|
|
|
|
|
|
SESSION_COOKIE_NAME,
|
|
|
|
|
|
SESSION_COOKIE_SAMESITE,
|
|
|
|
|
|
SESSION_COOKIE_SECURE,
|
|
|
|
|
|
SESSION_TOKEN_HEADER,
|
|
|
|
|
|
)
|
[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.db import local_session
|
2025-05-16 09:23:48 +03:00
|
|
|
|
from utils.logger import root_logger as logger
|
|
|
|
|
|
|
2025-05-30 14:05:50 +03:00
|
|
|
|
ADMIN_EMAILS = ADMIN_EMAILS_LIST.split(",")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AuthenticatedUser:
|
|
|
|
|
|
"""Аутентифицированный пользователь"""
|
|
|
|
|
|
|
2025-05-30 14:08:29 +03:00
|
|
|
|
def __init__(
|
2025-06-02 02:56:11 +03:00
|
|
|
|
self,
|
|
|
|
|
|
user_id: str,
|
|
|
|
|
|
username: str = "",
|
2025-08-17 16:33:54 +03:00
|
|
|
|
roles: list | None = None,
|
|
|
|
|
|
permissions: dict | None = None,
|
|
|
|
|
|
token: str | None = None,
|
2025-06-02 02:56:11 +03:00
|
|
|
|
) -> None:
|
2025-05-30 14:05:50 +03:00
|
|
|
|
self.user_id = user_id
|
|
|
|
|
|
self.username = username
|
|
|
|
|
|
self.roles = roles or []
|
|
|
|
|
|
self.permissions = permissions or {}
|
|
|
|
|
|
self.token = token
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def is_authenticated(self) -> bool:
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def display_name(self) -> str:
|
|
|
|
|
|
return self.username
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def identity(self) -> str:
|
|
|
|
|
|
return self.user_id
|
|
|
|
|
|
|
2025-05-16 09:23:48 +03:00
|
|
|
|
|
2025-05-19 11:25:41 +03:00
|
|
|
|
class AuthMiddleware:
|
2025-05-16 09:23:48 +03:00
|
|
|
|
"""
|
2025-05-30 14:05:50 +03:00
|
|
|
|
Единый middleware для обработки авторизации и аутентификации.
|
2025-05-29 12:37:39 +03:00
|
|
|
|
|
2025-05-19 11:25:41 +03:00
|
|
|
|
Основные функции:
|
|
|
|
|
|
1. Извлечение Bearer токена из заголовка Authorization или cookie
|
2025-06-02 21:50:58 +03:00
|
|
|
|
2. Проверка сессии через TokenStorage
|
2025-05-30 14:05:50 +03:00
|
|
|
|
3. Создание request.user и request.auth
|
|
|
|
|
|
4. Предоставление методов для установки/удаления cookies
|
2025-05-16 09:23:48 +03:00
|
|
|
|
"""
|
|
|
|
|
|
|
2025-06-02 02:56:11 +03:00
|
|
|
|
def __init__(self, app: ASGIApp) -> None:
|
2025-05-16 09:23:48 +03:00
|
|
|
|
self.app = app
|
2025-05-19 11:25:41 +03:00
|
|
|
|
self._context = None
|
2025-05-29 12:37:39 +03:00
|
|
|
|
|
2025-06-02 02:56:11 +03:00
|
|
|
|
async def authenticate_user(self, token: str) -> tuple[AuthCredentials, AuthenticatedUser | UnauthenticatedUser]:
|
2025-05-30 14:05:50 +03:00
|
|
|
|
"""Аутентифицирует пользователя по токену"""
|
|
|
|
|
|
if not token:
|
2025-06-02 02:56:11 +03:00
|
|
|
|
return AuthCredentials(
|
|
|
|
|
|
author_id=None, scopes={}, logged_in=False, error_message="no token", email=None, token=None
|
|
|
|
|
|
), UnauthenticatedUser()
|
2025-05-30 14:05:50 +03:00
|
|
|
|
|
|
|
|
|
|
# Проверяем сессию в Redis
|
2025-06-28 13:56:05 +03:00
|
|
|
|
try:
|
|
|
|
|
|
payload = await TokenManager.verify_session(token)
|
|
|
|
|
|
if not payload:
|
|
|
|
|
|
logger.debug("[auth.authenticate] Недействительный токен или сессия не найдена")
|
|
|
|
|
|
return AuthCredentials(
|
|
|
|
|
|
author_id=None,
|
|
|
|
|
|
scopes={},
|
|
|
|
|
|
logged_in=False,
|
|
|
|
|
|
error_message="Invalid token or session",
|
|
|
|
|
|
email=None,
|
|
|
|
|
|
token=None,
|
|
|
|
|
|
), UnauthenticatedUser()
|
2025-05-30 14:05:50 +03:00
|
|
|
|
|
2025-06-28 13:56:05 +03:00
|
|
|
|
with local_session() as session:
|
|
|
|
|
|
try:
|
e2e-fixing
fix: убран health endpoint, E2E тест использует корневой маршрут
- Убран health endpoint из main.py (не нужен)
- E2E тест теперь проверяет корневой маршрут / вместо /health
- Корневой маршрут доступен без логина, что подходит для проверки состояния сервера
- E2E тест с браузером работает корректно
docs: обновлен отчет о прогрессе E2E теста
- Убраны упоминания health endpoint
- Указано что используется корневой маршрут для проверки серверов
- Обновлен список измененных файлов
fix: исправлены GraphQL проблемы и E2E тест с браузером
- Добавлено поле success в тип CommonResult для совместимости с фронтендом
- Обновлены резолверы community, collection, topic для возврата поля success
- Исправлен E2E тест для работы с корневым маршрутом вместо health endpoint
- E2E тест теперь запускает браузер, авторизуется, находит сообщество в таблице
- Все GraphQL проблемы с полем success решены
- E2E тест работает правильно с браузером как требовалось
fix: исправлен поиск UI элементов в E2E тесте
- Добавлен правильный поиск кнопки удаления по CSS классу _delete-button_1qlfg_300
- Добавлены альтернативные способы поиска кнопки удаления (title, aria-label, символ ×)
- Добавлен правильный поиск модального окна с множественными селекторами
- Добавлен правильный поиск кнопки подтверждения в модальном окне
- E2E тест теперь полностью работает: находит кнопку удаления, модальное окно и кнопку подтверждения
- Обновлен отчет о прогрессе с полными результатами тестирования
fix: исправлен импорт require_any_permission в resolvers/collection.py
- Заменен импорт require_any_permission с auth.decorators на services.rbac
- Бэкенд сервер теперь запускается корректно
- E2E тест полностью работает: находит кнопку удаления, модальное окно и кнопку подтверждения
- Оба сервера (бэкенд и фронтенд) работают стабильно
fix: исправлен порядок импортов в resolvers/collection.py
- Перемещен импорт require_any_permission в правильное место
- E2E тест полностью работает: находит кнопку удаления, модальное окно и кнопку подтверждения
- Сообщество не удаляется из-за прав доступа - это нормальное поведение системы безопасности
feat: настроен HTTPS для локальной разработки с mkcert
2025-08-01 00:30:44 +03:00
|
|
|
|
# payload может быть словарем или объектом, обрабатываем оба случая
|
|
|
|
|
|
user_id = payload.user_id if hasattr(payload, "user_id") else payload.get("user_id")
|
|
|
|
|
|
if not user_id:
|
|
|
|
|
|
logger.debug("[auth.authenticate] user_id не найден в payload")
|
|
|
|
|
|
return AuthCredentials(
|
|
|
|
|
|
author_id=None,
|
|
|
|
|
|
scopes={},
|
|
|
|
|
|
logged_in=False,
|
|
|
|
|
|
error_message="Invalid token payload",
|
|
|
|
|
|
email=None,
|
|
|
|
|
|
token=None,
|
|
|
|
|
|
), UnauthenticatedUser()
|
|
|
|
|
|
|
|
|
|
|
|
author = session.query(Author).where(Author.id == user_id).one()
|
2025-06-28 13:56:05 +03:00
|
|
|
|
|
|
|
|
|
|
if author.is_locked():
|
|
|
|
|
|
logger.debug(f"[auth.authenticate] Аккаунт заблокирован: {author.id}")
|
|
|
|
|
|
return AuthCredentials(
|
|
|
|
|
|
author_id=None,
|
|
|
|
|
|
scopes={},
|
|
|
|
|
|
logged_in=False,
|
|
|
|
|
|
error_message="Account is locked",
|
|
|
|
|
|
email=None,
|
|
|
|
|
|
token=None,
|
|
|
|
|
|
), UnauthenticatedUser()
|
|
|
|
|
|
|
2025-07-02 22:49:20 +03:00
|
|
|
|
# Создаем пустой словарь разрешений
|
|
|
|
|
|
# Разрешения будут проверяться через RBAC систему по требованию
|
2025-07-03 00:20:10 +03:00
|
|
|
|
scopes: dict[str, Any] = {}
|
2025-06-28 13:56:05 +03:00
|
|
|
|
|
e2e-fixing
fix: убран health endpoint, E2E тест использует корневой маршрут
- Убран health endpoint из main.py (не нужен)
- E2E тест теперь проверяет корневой маршрут / вместо /health
- Корневой маршрут доступен без логина, что подходит для проверки состояния сервера
- E2E тест с браузером работает корректно
docs: обновлен отчет о прогрессе E2E теста
- Убраны упоминания health endpoint
- Указано что используется корневой маршрут для проверки серверов
- Обновлен список измененных файлов
fix: исправлены GraphQL проблемы и E2E тест с браузером
- Добавлено поле success в тип CommonResult для совместимости с фронтендом
- Обновлены резолверы community, collection, topic для возврата поля success
- Исправлен E2E тест для работы с корневым маршрутом вместо health endpoint
- E2E тест теперь запускает браузер, авторизуется, находит сообщество в таблице
- Все GraphQL проблемы с полем success решены
- E2E тест работает правильно с браузером как требовалось
fix: исправлен поиск UI элементов в E2E тесте
- Добавлен правильный поиск кнопки удаления по CSS классу _delete-button_1qlfg_300
- Добавлены альтернативные способы поиска кнопки удаления (title, aria-label, символ ×)
- Добавлен правильный поиск модального окна с множественными селекторами
- Добавлен правильный поиск кнопки подтверждения в модальном окне
- E2E тест теперь полностью работает: находит кнопку удаления, модальное окно и кнопку подтверждения
- Обновлен отчет о прогрессе с полными результатами тестирования
fix: исправлен импорт require_any_permission в resolvers/collection.py
- Заменен импорт require_any_permission с auth.decorators на services.rbac
- Бэкенд сервер теперь запускается корректно
- E2E тест полностью работает: находит кнопку удаления, модальное окно и кнопку подтверждения
- Оба сервера (бэкенд и фронтенд) работают стабильно
fix: исправлен порядок импортов в resolvers/collection.py
- Перемещен импорт require_any_permission в правильное место
- E2E тест полностью работает: находит кнопку удаления, модальное окно и кнопку подтверждения
- Сообщество не удаляется из-за прав доступа - это нормальное поведение системы безопасности
feat: настроен HTTPS для локальной разработки с mkcert
2025-08-01 00:30:44 +03:00
|
|
|
|
# Роли пользователя будут определяться в контексте конкретной операции
|
|
|
|
|
|
# через RBAC систему, а не здесь
|
2025-08-12 18:23:53 +03:00
|
|
|
|
roles: list[str] = []
|
2025-06-28 13:56:05 +03:00
|
|
|
|
|
|
|
|
|
|
# Обновляем last_seen
|
|
|
|
|
|
author.last_seen = int(time.time())
|
|
|
|
|
|
session.commit()
|
|
|
|
|
|
|
|
|
|
|
|
# Создаем объекты авторизации с сохранением токена
|
|
|
|
|
|
credentials = AuthCredentials(
|
|
|
|
|
|
author_id=author.id,
|
|
|
|
|
|
scopes=scopes,
|
|
|
|
|
|
logged_in=True,
|
|
|
|
|
|
error_message="",
|
|
|
|
|
|
email=author.email,
|
|
|
|
|
|
token=token,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
user = AuthenticatedUser(
|
|
|
|
|
|
user_id=str(author.id),
|
|
|
|
|
|
username=author.slug or author.email or "",
|
|
|
|
|
|
roles=roles,
|
|
|
|
|
|
permissions=scopes,
|
|
|
|
|
|
token=token,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
logger.debug(f"[auth.authenticate] Успешная аутентификация: {author.email}")
|
|
|
|
|
|
return credentials, user
|
|
|
|
|
|
|
|
|
|
|
|
except exc.NoResultFound:
|
|
|
|
|
|
logger.debug("[auth.authenticate] Пользователь не найден в базе данных")
|
2025-06-02 02:56:11 +03:00
|
|
|
|
return AuthCredentials(
|
|
|
|
|
|
author_id=None,
|
|
|
|
|
|
scopes={},
|
|
|
|
|
|
logged_in=False,
|
2025-06-28 13:56:05 +03:00
|
|
|
|
error_message="User not found",
|
2025-06-02 02:56:11 +03:00
|
|
|
|
email=None,
|
|
|
|
|
|
token=None,
|
|
|
|
|
|
), UnauthenticatedUser()
|
2025-06-28 13:56:05 +03:00
|
|
|
|
except Exception as e:
|
2025-08-27 18:31:51 +03:00
|
|
|
|
logger.warning(f"[auth.authenticate] Ошибка при работе с базой данных: {e}")
|
2025-06-28 13:56:05 +03:00
|
|
|
|
return AuthCredentials(
|
|
|
|
|
|
author_id=None, scopes={}, logged_in=False, error_message=str(e), email=None, token=None
|
|
|
|
|
|
), UnauthenticatedUser()
|
|
|
|
|
|
except Exception as e:
|
2025-08-27 18:31:51 +03:00
|
|
|
|
logger.warning(f"[auth.authenticate] Ошибка при проверке сессии: {e}")
|
2025-06-28 13:56:05 +03:00
|
|
|
|
return AuthCredentials(
|
|
|
|
|
|
author_id=None, scopes={}, logged_in=False, error_message=str(e), email=None, token=None
|
|
|
|
|
|
), UnauthenticatedUser()
|
2025-06-02 02:56:11 +03:00
|
|
|
|
|
|
|
|
|
|
async def __call__(
|
|
|
|
|
|
self,
|
|
|
|
|
|
scope: MutableMapping[str, Any],
|
|
|
|
|
|
receive: Callable[[], Awaitable[MutableMapping[str, Any]]],
|
|
|
|
|
|
send: Callable[[MutableMapping[str, Any]], Awaitable[None]],
|
|
|
|
|
|
) -> None:
|
2025-05-19 11:25:41 +03:00
|
|
|
|
"""Обработка ASGI запроса"""
|
2025-05-16 09:23:48 +03:00
|
|
|
|
if scope["type"] != "http":
|
|
|
|
|
|
await self.app(scope, receive, send)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
e2e-fixing
fix: убран health endpoint, E2E тест использует корневой маршрут
- Убран health endpoint из main.py (не нужен)
- E2E тест теперь проверяет корневой маршрут / вместо /health
- Корневой маршрут доступен без логина, что подходит для проверки состояния сервера
- E2E тест с браузером работает корректно
docs: обновлен отчет о прогрессе E2E теста
- Убраны упоминания health endpoint
- Указано что используется корневой маршрут для проверки серверов
- Обновлен список измененных файлов
fix: исправлены GraphQL проблемы и E2E тест с браузером
- Добавлено поле success в тип CommonResult для совместимости с фронтендом
- Обновлены резолверы community, collection, topic для возврата поля success
- Исправлен E2E тест для работы с корневым маршрутом вместо health endpoint
- E2E тест теперь запускает браузер, авторизуется, находит сообщество в таблице
- Все GraphQL проблемы с полем success решены
- E2E тест работает правильно с браузером как требовалось
fix: исправлен поиск UI элементов в E2E тесте
- Добавлен правильный поиск кнопки удаления по CSS классу _delete-button_1qlfg_300
- Добавлены альтернативные способы поиска кнопки удаления (title, aria-label, символ ×)
- Добавлен правильный поиск модального окна с множественными селекторами
- Добавлен правильный поиск кнопки подтверждения в модальном окне
- E2E тест теперь полностью работает: находит кнопку удаления, модальное окно и кнопку подтверждения
- Обновлен отчет о прогрессе с полными результатами тестирования
fix: исправлен импорт require_any_permission в resolvers/collection.py
- Заменен импорт require_any_permission с auth.decorators на services.rbac
- Бэкенд сервер теперь запускается корректно
- E2E тест полностью работает: находит кнопку удаления, модальное окно и кнопку подтверждения
- Оба сервера (бэкенд и фронтенд) работают стабильно
fix: исправлен порядок импортов в resolvers/collection.py
- Перемещен импорт require_any_permission в правильное место
- E2E тест полностью работает: находит кнопку удаления, модальное окно и кнопку подтверждения
- Сообщество не удаляется из-за прав доступа - это нормальное поведение системы безопасности
feat: настроен HTTPS для локальной разработки с mkcert
2025-08-01 00:30:44 +03:00
|
|
|
|
# Извлекаем заголовки используя тот же механизм, что и get_safe_headers
|
|
|
|
|
|
headers = {}
|
|
|
|
|
|
|
|
|
|
|
|
# Первый приоритет: scope из ASGI (самый надежный источник)
|
|
|
|
|
|
if "headers" in scope:
|
|
|
|
|
|
scope_headers = scope.get("headers", [])
|
|
|
|
|
|
if scope_headers:
|
|
|
|
|
|
headers.update({k.decode("utf-8").lower(): v.decode("utf-8") for k, v in scope_headers})
|
|
|
|
|
|
|
|
|
|
|
|
# Используем тот же механизм получения токена, что и в декораторе
|
2025-05-16 09:23:48 +03:00
|
|
|
|
token = None
|
|
|
|
|
|
|
e2e-fixing
fix: убран health endpoint, E2E тест использует корневой маршрут
- Убран health endpoint из main.py (не нужен)
- E2E тест теперь проверяет корневой маршрут / вместо /health
- Корневой маршрут доступен без логина, что подходит для проверки состояния сервера
- E2E тест с браузером работает корректно
docs: обновлен отчет о прогрессе E2E теста
- Убраны упоминания health endpoint
- Указано что используется корневой маршрут для проверки серверов
- Обновлен список измененных файлов
fix: исправлены GraphQL проблемы и E2E тест с браузером
- Добавлено поле success в тип CommonResult для совместимости с фронтендом
- Обновлены резолверы community, collection, topic для возврата поля success
- Исправлен E2E тест для работы с корневым маршрутом вместо health endpoint
- E2E тест теперь запускает браузер, авторизуется, находит сообщество в таблице
- Все GraphQL проблемы с полем success решены
- E2E тест работает правильно с браузером как требовалось
fix: исправлен поиск UI элементов в E2E тесте
- Добавлен правильный поиск кнопки удаления по CSS классу _delete-button_1qlfg_300
- Добавлены альтернативные способы поиска кнопки удаления (title, aria-label, символ ×)
- Добавлен правильный поиск модального окна с множественными селекторами
- Добавлен правильный поиск кнопки подтверждения в модальном окне
- E2E тест теперь полностью работает: находит кнопку удаления, модальное окно и кнопку подтверждения
- Обновлен отчет о прогрессе с полными результатами тестирования
fix: исправлен импорт require_any_permission в resolvers/collection.py
- Заменен импорт require_any_permission с auth.decorators на services.rbac
- Бэкенд сервер теперь запускается корректно
- E2E тест полностью работает: находит кнопку удаления, модальное окно и кнопку подтверждения
- Оба сервера (бэкенд и фронтенд) работают стабильно
fix: исправлен порядок импортов в resolvers/collection.py
- Перемещен импорт require_any_permission в правильное место
- E2E тест полностью работает: находит кнопку удаления, модальное окно и кнопку подтверждения
- Сообщество не удаляется из-за прав доступа - это нормальное поведение системы безопасности
feat: настроен HTTPS для локальной разработки с mkcert
2025-08-01 00:30:44 +03:00
|
|
|
|
# 0. Проверяем сохраненный токен в scope (приоритет)
|
|
|
|
|
|
if "auth_token" in scope:
|
|
|
|
|
|
token = scope["auth_token"]
|
|
|
|
|
|
|
|
|
|
|
|
# 1. Проверяем заголовок Authorization
|
|
|
|
|
|
if not token:
|
|
|
|
|
|
auth_header = headers.get("authorization", "")
|
|
|
|
|
|
if auth_header:
|
2025-09-28 17:26:23 +03:00
|
|
|
|
token = auth_header[7:].strip() if auth_header.startswith("Bearer ") else auth_header.strip()
|
e2e-fixing
fix: убран health endpoint, E2E тест использует корневой маршрут
- Убран health endpoint из main.py (не нужен)
- E2E тест теперь проверяет корневой маршрут / вместо /health
- Корневой маршрут доступен без логина, что подходит для проверки состояния сервера
- E2E тест с браузером работает корректно
docs: обновлен отчет о прогрессе E2E теста
- Убраны упоминания health endpoint
- Указано что используется корневой маршрут для проверки серверов
- Обновлен список измененных файлов
fix: исправлены GraphQL проблемы и E2E тест с браузером
- Добавлено поле success в тип CommonResult для совместимости с фронтендом
- Обновлены резолверы community, collection, topic для возврата поля success
- Исправлен E2E тест для работы с корневым маршрутом вместо health endpoint
- E2E тест теперь запускает браузер, авторизуется, находит сообщество в таблице
- Все GraphQL проблемы с полем success решены
- E2E тест работает правильно с браузером как требовалось
fix: исправлен поиск UI элементов в E2E тесте
- Добавлен правильный поиск кнопки удаления по CSS классу _delete-button_1qlfg_300
- Добавлены альтернативные способы поиска кнопки удаления (title, aria-label, символ ×)
- Добавлен правильный поиск модального окна с множественными селекторами
- Добавлен правильный поиск кнопки подтверждения в модальном окне
- E2E тест теперь полностью работает: находит кнопку удаления, модальное окно и кнопку подтверждения
- Обновлен отчет о прогрессе с полными результатами тестирования
fix: исправлен импорт require_any_permission в resolvers/collection.py
- Заменен импорт require_any_permission с auth.decorators на services.rbac
- Бэкенд сервер теперь запускается корректно
- E2E тест полностью работает: находит кнопку удаления, модальное окно и кнопку подтверждения
- Оба сервера (бэкенд и фронтенд) работают стабильно
fix: исправлен порядок импортов в resolvers/collection.py
- Перемещен импорт require_any_permission в правильное место
- E2E тест полностью работает: находит кнопку удаления, модальное окно и кнопку подтверждения
- Сообщество не удаляется из-за прав доступа - это нормальное поведение системы безопасности
feat: настроен HTTPS для локальной разработки с mkcert
2025-08-01 00:30:44 +03:00
|
|
|
|
|
|
|
|
|
|
# 2. Проверяем основной заголовок авторизации, если Authorization не найден
|
|
|
|
|
|
if not token:
|
|
|
|
|
|
auth_header = headers.get(SESSION_TOKEN_HEADER.lower(), "")
|
|
|
|
|
|
if auth_header:
|
2025-09-28 17:26:23 +03:00
|
|
|
|
token = auth_header[7:].strip() if auth_header.startswith("Bearer ") else auth_header.strip()
|
e2e-fixing
fix: убран health endpoint, E2E тест использует корневой маршрут
- Убран health endpoint из main.py (не нужен)
- E2E тест теперь проверяет корневой маршрут / вместо /health
- Корневой маршрут доступен без логина, что подходит для проверки состояния сервера
- E2E тест с браузером работает корректно
docs: обновлен отчет о прогрессе E2E теста
- Убраны упоминания health endpoint
- Указано что используется корневой маршрут для проверки серверов
- Обновлен список измененных файлов
fix: исправлены GraphQL проблемы и E2E тест с браузером
- Добавлено поле success в тип CommonResult для совместимости с фронтендом
- Обновлены резолверы community, collection, topic для возврата поля success
- Исправлен E2E тест для работы с корневым маршрутом вместо health endpoint
- E2E тест теперь запускает браузер, авторизуется, находит сообщество в таблице
- Все GraphQL проблемы с полем success решены
- E2E тест работает правильно с браузером как требовалось
fix: исправлен поиск UI элементов в E2E тесте
- Добавлен правильный поиск кнопки удаления по CSS классу _delete-button_1qlfg_300
- Добавлены альтернативные способы поиска кнопки удаления (title, aria-label, символ ×)
- Добавлен правильный поиск модального окна с множественными селекторами
- Добавлен правильный поиск кнопки подтверждения в модальном окне
- E2E тест теперь полностью работает: находит кнопку удаления, модальное окно и кнопку подтверждения
- Обновлен отчет о прогрессе с полными результатами тестирования
fix: исправлен импорт require_any_permission в resolvers/collection.py
- Заменен импорт require_any_permission с auth.decorators на services.rbac
- Бэкенд сервер теперь запускается корректно
- E2E тест полностью работает: находит кнопку удаления, модальное окно и кнопку подтверждения
- Оба сервера (бэкенд и фронтенд) работают стабильно
fix: исправлен порядок импортов в resolvers/collection.py
- Перемещен импорт require_any_permission в правильное место
- E2E тест полностью работает: находит кнопку удаления, модальное окно и кнопку подтверждения
- Сообщество не удаляется из-за прав доступа - это нормальное поведение системы безопасности
feat: настроен HTTPS для локальной разработки с mkcert
2025-08-01 00:30:44 +03:00
|
|
|
|
|
|
|
|
|
|
# 3. Проверяем cookie
|
2025-05-16 09:23:48 +03:00
|
|
|
|
if not token:
|
|
|
|
|
|
cookies = headers.get("cookie", "")
|
2025-09-28 17:26:23 +03:00
|
|
|
|
if cookies:
|
|
|
|
|
|
cookie_items = cookies.split(";")
|
|
|
|
|
|
for item in cookie_items:
|
|
|
|
|
|
if "=" in item:
|
|
|
|
|
|
name, value = item.split("=", 1)
|
|
|
|
|
|
cookie_name = name.strip()
|
|
|
|
|
|
if cookie_name == SESSION_COOKIE_NAME:
|
|
|
|
|
|
token = value.strip()
|
|
|
|
|
|
break
|
e2e-fixing
fix: убран health endpoint, E2E тест использует корневой маршрут
- Убран health endpoint из main.py (не нужен)
- E2E тест теперь проверяет корневой маршрут / вместо /health
- Корневой маршрут доступен без логина, что подходит для проверки состояния сервера
- E2E тест с браузером работает корректно
docs: обновлен отчет о прогрессе E2E теста
- Убраны упоминания health endpoint
- Указано что используется корневой маршрут для проверки серверов
- Обновлен список измененных файлов
fix: исправлены GraphQL проблемы и E2E тест с браузером
- Добавлено поле success в тип CommonResult для совместимости с фронтендом
- Обновлены резолверы community, collection, topic для возврата поля success
- Исправлен E2E тест для работы с корневым маршрутом вместо health endpoint
- E2E тест теперь запускает браузер, авторизуется, находит сообщество в таблице
- Все GraphQL проблемы с полем success решены
- E2E тест работает правильно с браузером как требовалось
fix: исправлен поиск UI элементов в E2E тесте
- Добавлен правильный поиск кнопки удаления по CSS классу _delete-button_1qlfg_300
- Добавлены альтернативные способы поиска кнопки удаления (title, aria-label, символ ×)
- Добавлен правильный поиск модального окна с множественными селекторами
- Добавлен правильный поиск кнопки подтверждения в модальном окне
- E2E тест теперь полностью работает: находит кнопку удаления, модальное окно и кнопку подтверждения
- Обновлен отчет о прогрессе с полными результатами тестирования
fix: исправлен импорт require_any_permission в resolvers/collection.py
- Заменен импорт require_any_permission с auth.decorators на services.rbac
- Бэкенд сервер теперь запускается корректно
- E2E тест полностью работает: находит кнопку удаления, модальное окно и кнопку подтверждения
- Оба сервера (бэкенд и фронтенд) работают стабильно
fix: исправлен порядок импортов в resolvers/collection.py
- Перемещен импорт require_any_permission в правильное место
- E2E тест полностью работает: находит кнопку удаления, модальное окно и кнопку подтверждения
- Сообщество не удаляется из-за прав доступа - это нормальное поведение системы безопасности
feat: настроен HTTPS для локальной разработки с mkcert
2025-08-01 00:30:44 +03:00
|
|
|
|
|
2025-05-30 14:05:50 +03:00
|
|
|
|
# Аутентифицируем пользователя
|
2025-06-02 02:56:11 +03:00
|
|
|
|
auth, user = await self.authenticate_user(token or "")
|
2025-05-30 14:08:29 +03:00
|
|
|
|
|
2025-05-30 14:05:50 +03:00
|
|
|
|
# Добавляем в scope данные авторизации и пользователя
|
|
|
|
|
|
scope["auth"] = auth
|
|
|
|
|
|
scope["user"] = user
|
2025-05-30 14:08:29 +03:00
|
|
|
|
|
e2e-fixing
fix: убран health endpoint, E2E тест использует корневой маршрут
- Убран health endpoint из main.py (не нужен)
- E2E тест теперь проверяет корневой маршрут / вместо /health
- Корневой маршрут доступен без логина, что подходит для проверки состояния сервера
- E2E тест с браузером работает корректно
docs: обновлен отчет о прогрессе E2E теста
- Убраны упоминания health endpoint
- Указано что используется корневой маршрут для проверки серверов
- Обновлен список измененных файлов
fix: исправлены GraphQL проблемы и E2E тест с браузером
- Добавлено поле success в тип CommonResult для совместимости с фронтендом
- Обновлены резолверы community, collection, topic для возврата поля success
- Исправлен E2E тест для работы с корневым маршрутом вместо health endpoint
- E2E тест теперь запускает браузер, авторизуется, находит сообщество в таблице
- Все GraphQL проблемы с полем success решены
- E2E тест работает правильно с браузером как требовалось
fix: исправлен поиск UI элементов в E2E тесте
- Добавлен правильный поиск кнопки удаления по CSS классу _delete-button_1qlfg_300
- Добавлены альтернативные способы поиска кнопки удаления (title, aria-label, символ ×)
- Добавлен правильный поиск модального окна с множественными селекторами
- Добавлен правильный поиск кнопки подтверждения в модальном окне
- E2E тест теперь полностью работает: находит кнопку удаления, модальное окно и кнопку подтверждения
- Обновлен отчет о прогрессе с полными результатами тестирования
fix: исправлен импорт require_any_permission в resolvers/collection.py
- Заменен импорт require_any_permission с auth.decorators на services.rbac
- Бэкенд сервер теперь запускается корректно
- E2E тест полностью работает: находит кнопку удаления, модальное окно и кнопку подтверждения
- Оба сервера (бэкенд и фронтенд) работают стабильно
fix: исправлен порядок импортов в resolvers/collection.py
- Перемещен импорт require_any_permission в правильное место
- E2E тест полностью работает: находит кнопку удаления, модальное окно и кнопку подтверждения
- Сообщество не удаляется из-за прав доступа - это нормальное поведение системы безопасности
feat: настроен HTTPS для локальной разработки с mkcert
2025-08-01 00:30:44 +03:00
|
|
|
|
# Сохраняем токен в scope для использования в последующих запросах
|
2025-05-16 09:23:48 +03:00
|
|
|
|
if token:
|
e2e-fixing
fix: убран health endpoint, E2E тест использует корневой маршрут
- Убран health endpoint из main.py (не нужен)
- E2E тест теперь проверяет корневой маршрут / вместо /health
- Корневой маршрут доступен без логина, что подходит для проверки состояния сервера
- E2E тест с браузером работает корректно
docs: обновлен отчет о прогрессе E2E теста
- Убраны упоминания health endpoint
- Указано что используется корневой маршрут для проверки серверов
- Обновлен список измененных файлов
fix: исправлены GraphQL проблемы и E2E тест с браузером
- Добавлено поле success в тип CommonResult для совместимости с фронтендом
- Обновлены резолверы community, collection, topic для возврата поля success
- Исправлен E2E тест для работы с корневым маршрутом вместо health endpoint
- E2E тест теперь запускает браузер, авторизуется, находит сообщество в таблице
- Все GraphQL проблемы с полем success решены
- E2E тест работает правильно с браузером как требовалось
fix: исправлен поиск UI элементов в E2E тесте
- Добавлен правильный поиск кнопки удаления по CSS классу _delete-button_1qlfg_300
- Добавлены альтернативные способы поиска кнопки удаления (title, aria-label, символ ×)
- Добавлен правильный поиск модального окна с множественными селекторами
- Добавлен правильный поиск кнопки подтверждения в модальном окне
- E2E тест теперь полностью работает: находит кнопку удаления, модальное окно и кнопку подтверждения
- Обновлен отчет о прогрессе с полными результатами тестирования
fix: исправлен импорт require_any_permission в resolvers/collection.py
- Заменен импорт require_any_permission с auth.decorators на services.rbac
- Бэкенд сервер теперь запускается корректно
- E2E тест полностью работает: находит кнопку удаления, модальное окно и кнопку подтверждения
- Оба сервера (бэкенд и фронтенд) работают стабильно
fix: исправлен порядок импортов в resolvers/collection.py
- Перемещен импорт require_any_permission в правильное место
- E2E тест полностью работает: находит кнопку удаления, модальное окно и кнопку подтверждения
- Сообщество не удаляется из-за прав доступа - это нормальное поведение системы безопасности
feat: настроен HTTPS для локальной разработки с mkcert
2025-08-01 00:30:44 +03:00
|
|
|
|
scope["auth_token"] = token
|
2025-05-16 09:23:48 +03:00
|
|
|
|
|
|
|
|
|
|
await self.app(scope, receive, send)
|
2025-05-29 12:37:39 +03:00
|
|
|
|
|
2025-06-02 02:56:11 +03:00
|
|
|
|
def set_context(self, context) -> None:
|
2025-05-19 11:25:41 +03:00
|
|
|
|
"""Сохраняет ссылку на контекст GraphQL запроса"""
|
|
|
|
|
|
self._context = context
|
2025-05-29 12:37:39 +03:00
|
|
|
|
|
2025-06-02 21:50:58 +03:00
|
|
|
|
def set_cookie(self, key: str, value: str, **options: Any) -> None:
|
2025-05-20 00:00:24 +03:00
|
|
|
|
"""
|
|
|
|
|
|
Устанавливает cookie в ответе
|
2025-05-29 12:37:39 +03:00
|
|
|
|
|
2025-05-20 00:00:24 +03:00
|
|
|
|
Args:
|
|
|
|
|
|
key: Имя cookie
|
|
|
|
|
|
value: Значение cookie
|
|
|
|
|
|
**options: Дополнительные параметры (httponly, secure, max_age, etc.)
|
|
|
|
|
|
"""
|
|
|
|
|
|
success = False
|
2025-05-29 12:37:39 +03:00
|
|
|
|
|
2025-05-20 00:00:24 +03:00
|
|
|
|
# Способ 1: Через response
|
2025-05-19 11:25:41 +03:00
|
|
|
|
if self._context and "response" in self._context and hasattr(self._context["response"], "set_cookie"):
|
2025-05-20 00:00:24 +03:00
|
|
|
|
try:
|
|
|
|
|
|
self._context["response"].set_cookie(key, value, **options)
|
|
|
|
|
|
success = True
|
|
|
|
|
|
except Exception as e:
|
2025-06-02 02:56:11 +03:00
|
|
|
|
logger.error(f"[middleware] Ошибка при установке cookie {key} через response: {e!s}")
|
2025-05-29 12:37:39 +03:00
|
|
|
|
|
2025-05-20 00:00:24 +03:00
|
|
|
|
# Способ 2: Через собственный response в контексте
|
|
|
|
|
|
if not success and hasattr(self, "_response") and self._response and hasattr(self._response, "set_cookie"):
|
|
|
|
|
|
try:
|
|
|
|
|
|
self._response.set_cookie(key, value, **options)
|
|
|
|
|
|
success = True
|
|
|
|
|
|
except Exception as e:
|
2025-06-02 02:56:11 +03:00
|
|
|
|
logger.error(f"[middleware] Ошибка при установке cookie {key} через _response: {e!s}")
|
2025-05-29 12:37:39 +03:00
|
|
|
|
|
2025-05-20 00:00:24 +03:00
|
|
|
|
if not success:
|
|
|
|
|
|
logger.error(f"[middleware] Не удалось установить cookie {key}: объекты response недоступны")
|
2025-05-16 09:23:48 +03:00
|
|
|
|
|
2025-06-02 21:50:58 +03:00
|
|
|
|
def delete_cookie(self, key: str, **options: Any) -> None:
|
2025-05-20 00:00:24 +03:00
|
|
|
|
"""
|
|
|
|
|
|
Удаляет cookie из ответа
|
|
|
|
|
|
"""
|
|
|
|
|
|
success = False
|
2025-05-29 12:37:39 +03:00
|
|
|
|
|
2025-05-20 00:00:24 +03:00
|
|
|
|
# Способ 1: Через response
|
2025-05-19 11:25:41 +03:00
|
|
|
|
if self._context and "response" in self._context and hasattr(self._context["response"], "delete_cookie"):
|
2025-05-20 00:00:24 +03:00
|
|
|
|
try:
|
|
|
|
|
|
self._context["response"].delete_cookie(key, **options)
|
|
|
|
|
|
success = True
|
|
|
|
|
|
except Exception as e:
|
2025-06-02 02:56:11 +03:00
|
|
|
|
logger.error(f"[middleware] Ошибка при удалении cookie {key} через response: {e!s}")
|
2025-05-29 12:37:39 +03:00
|
|
|
|
|
2025-05-20 00:00:24 +03:00
|
|
|
|
# Способ 2: Через собственный response в контексте
|
|
|
|
|
|
if not success and hasattr(self, "_response") and self._response and hasattr(self._response, "delete_cookie"):
|
|
|
|
|
|
try:
|
|
|
|
|
|
self._response.delete_cookie(key, **options)
|
|
|
|
|
|
success = True
|
|
|
|
|
|
except Exception as e:
|
2025-06-02 02:56:11 +03:00
|
|
|
|
logger.error(f"[middleware] Ошибка при удалении cookie {key} через _response: {e!s}")
|
2025-05-29 12:37:39 +03:00
|
|
|
|
|
2025-05-20 00:00:24 +03:00
|
|
|
|
if not success:
|
|
|
|
|
|
logger.error(f"[middleware] Не удалось удалить cookie {key}: объекты response недоступны")
|
2025-05-16 09:23:48 +03:00
|
|
|
|
|
2025-06-02 02:56:11 +03:00
|
|
|
|
async def resolve(
|
2025-06-02 21:50:58 +03:00
|
|
|
|
self, next_resolver: Callable[..., Any], root: Any, info: GraphQLResolveInfo, *args: Any, **kwargs: Any
|
2025-06-02 02:56:11 +03:00
|
|
|
|
) -> Any:
|
2025-05-16 09:23:48 +03:00
|
|
|
|
"""
|
|
|
|
|
|
Middleware для обработки запросов GraphQL.
|
|
|
|
|
|
Добавляет методы для установки cookie в контекст.
|
|
|
|
|
|
"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
# Получаем доступ к контексту запроса
|
|
|
|
|
|
context = info.context
|
2025-05-29 12:37:39 +03:00
|
|
|
|
|
2025-05-16 09:23:48 +03:00
|
|
|
|
# Сохраняем ссылку на контекст
|
2025-05-19 11:25:41 +03:00
|
|
|
|
self.set_context(context)
|
2025-05-29 12:37:39 +03:00
|
|
|
|
|
2025-05-16 09:23:48 +03:00
|
|
|
|
# Добавляем себя как объект, содержащий утилитные методы
|
|
|
|
|
|
context["extensions"] = self
|
2025-05-29 12:37:39 +03:00
|
|
|
|
|
2025-05-20 00:00:24 +03:00
|
|
|
|
# Проверяем наличие response в контексте
|
|
|
|
|
|
if "response" not in context or not context["response"]:
|
|
|
|
|
|
context["response"] = JSONResponse({})
|
2025-05-29 12:37:39 +03:00
|
|
|
|
|
2025-06-02 21:50:58 +03:00
|
|
|
|
return await next_resolver(root, info, *args, **kwargs)
|
2025-05-16 09:23:48 +03:00
|
|
|
|
except Exception as e:
|
2025-06-02 02:56:11 +03:00
|
|
|
|
logger.error(f"[AuthMiddleware] Ошибка в GraphQL resolve: {e!s}")
|
2025-05-16 09:23:48 +03:00
|
|
|
|
raise
|
2025-05-29 12:37:39 +03:00
|
|
|
|
|
2025-05-22 04:34:30 +03:00
|
|
|
|
async def process_result(self, request: Request, result: Any) -> Response:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Обрабатывает результат GraphQL запроса, поддерживая установку cookie
|
2025-05-29 12:37:39 +03:00
|
|
|
|
|
2025-05-22 04:34:30 +03:00
|
|
|
|
Args:
|
|
|
|
|
|
request: Starlette Request объект
|
|
|
|
|
|
result: результат GraphQL запроса (dict или Response)
|
2025-05-29 12:37:39 +03:00
|
|
|
|
|
2025-05-22 04:34:30 +03:00
|
|
|
|
Returns:
|
|
|
|
|
|
Response: HTTP-ответ с результатом и cookie (если необходимо)
|
|
|
|
|
|
"""
|
2025-05-29 12:37:39 +03:00
|
|
|
|
|
2025-05-22 04:34:30 +03:00
|
|
|
|
# Проверяем, является ли result уже объектом Response
|
2025-09-28 12:22:37 +03:00
|
|
|
|
response = result if isinstance(result, Response) else JSONResponse(result)
|
2025-05-29 12:37:39 +03:00
|
|
|
|
|
2025-05-22 04:34:30 +03:00
|
|
|
|
# Проверяем, был ли токен в запросе или ответе
|
|
|
|
|
|
if request.method == "POST":
|
|
|
|
|
|
try:
|
|
|
|
|
|
data = await request.json()
|
|
|
|
|
|
op_name = data.get("operationName", "").lower()
|
2025-05-29 12:37:39 +03:00
|
|
|
|
|
2025-05-22 04:34:30 +03:00
|
|
|
|
# Если это операция logout, удаляем cookie
|
2025-09-28 17:26:23 +03:00
|
|
|
|
if op_name == "logout":
|
2025-05-22 04:34:30 +03:00
|
|
|
|
response.delete_cookie(
|
|
|
|
|
|
key=SESSION_COOKIE_NAME,
|
|
|
|
|
|
secure=SESSION_COOKIE_SECURE,
|
|
|
|
|
|
httponly=SESSION_COOKIE_HTTPONLY,
|
2025-09-28 12:22:37 +03:00
|
|
|
|
samesite=SESSION_COOKIE_SAMESITE
|
|
|
|
|
|
if SESSION_COOKIE_SAMESITE in ["strict", "lax", "none"]
|
|
|
|
|
|
else "none",
|
2025-09-28 17:26:23 +03:00
|
|
|
|
domain=SESSION_COOKIE_DOMAIN,
|
2025-05-22 04:34:30 +03:00
|
|
|
|
)
|
|
|
|
|
|
except Exception as e:
|
2025-06-02 02:56:11 +03:00
|
|
|
|
logger.error(f"[process_result] Ошибка при обработке POST запроса: {e!s}")
|
2025-05-29 12:37:39 +03:00
|
|
|
|
|
2025-05-22 04:34:30 +03:00
|
|
|
|
return response
|
2025-05-29 12:37:39 +03:00
|
|
|
|
|
|
|
|
|
|
|
2025-05-22 04:34:30 +03:00
|
|
|
|
# Создаем единый экземпляр AuthMiddleware для использования с GraphQL
|
2025-06-02 02:56:11 +03:00
|
|
|
|
async def _dummy_app(
|
|
|
|
|
|
scope: MutableMapping[str, Any],
|
|
|
|
|
|
receive: Callable[[], Awaitable[MutableMapping[str, Any]]],
|
|
|
|
|
|
send: Callable[[MutableMapping[str, Any]], Awaitable[None]],
|
|
|
|
|
|
) -> None:
|
|
|
|
|
|
"""Dummy ASGI app for middleware initialization"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
auth_middleware = AuthMiddleware(_dummy_app)
|