This commit is contained in:
@@ -44,12 +44,6 @@ class EnhancedGraphQLHTTPHandler(GraphQLHTTPHandler):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"[graphql] Ошибка при получении заголовков: {e}")
|
logger.debug(f"[graphql] Ошибка при получении заголовков: {e}")
|
||||||
|
|
||||||
logger.debug(f"[graphql] Заголовки в get_context_for_request: {list(headers.keys())}")
|
|
||||||
if "authorization" in headers:
|
|
||||||
logger.debug(f"[graphql] Authorization header найден: {headers['authorization'][:50]}...")
|
|
||||||
else:
|
|
||||||
logger.debug("[graphql] Authorization header НЕ найден")
|
|
||||||
|
|
||||||
# Получаем стандартный контекст от базового класса
|
# Получаем стандартный контекст от базового класса
|
||||||
context = await super().get_context_for_request(request, data)
|
context = await super().get_context_for_request(request, data)
|
||||||
|
|
||||||
@@ -67,15 +61,6 @@ class EnhancedGraphQLHTTPHandler(GraphQLHTTPHandler):
|
|||||||
auth_cred: Any | None = request.scope.get("auth")
|
auth_cred: Any | None = request.scope.get("auth")
|
||||||
context["auth"] = auth_cred
|
context["auth"] = auth_cred
|
||||||
# Безопасно логируем информацию о типе объекта auth
|
# Безопасно логируем информацию о типе объекта auth
|
||||||
logger.debug(f"[graphql] Добавлены данные авторизации в контекст из scope: {type(auth_cred).__name__}")
|
|
||||||
|
|
||||||
# Проверяем, есть ли токен в auth_cred
|
|
||||||
if auth_cred is not None and hasattr(auth_cred, "token") and auth_cred.token:
|
|
||||||
token_val = auth_cred.token
|
|
||||||
token_len = len(token_val) if hasattr(token_val, "__len__") else 0
|
|
||||||
logger.debug(f"[graphql] Токен найден в auth_cred: {token_len}")
|
|
||||||
else:
|
|
||||||
logger.debug("[graphql] Токен НЕ найден в auth_cred")
|
|
||||||
|
|
||||||
# Добавляем author_id в контекст для RBAC
|
# Добавляем author_id в контекст для RBAC
|
||||||
author_id = None
|
author_id = None
|
||||||
@@ -89,16 +74,8 @@ class EnhancedGraphQLHTTPHandler(GraphQLHTTPHandler):
|
|||||||
try:
|
try:
|
||||||
author_id_int = int(str(author_id).strip())
|
author_id_int = int(str(author_id).strip())
|
||||||
context["author"] = {"id": author_id_int}
|
context["author"] = {"id": author_id_int}
|
||||||
logger.debug(f"[graphql] Добавлен author_id в контекст: {author_id_int}")
|
|
||||||
except (ValueError, TypeError) as e:
|
except (ValueError, TypeError) as e:
|
||||||
logger.error(f"[graphql] Ошибка преобразования author_id {author_id}: {e}")
|
logger.error(f"[graphql] Ошибка преобразования author_id {author_id}: {e}")
|
||||||
context["author"] = {"id": author_id}
|
context["author"] = {"id": author_id}
|
||||||
logger.debug(f"[graphql] Добавлен author_id как строка: {author_id}")
|
|
||||||
else:
|
|
||||||
logger.debug("[graphql] author_id не найден в auth_cred")
|
|
||||||
else:
|
|
||||||
logger.debug("[graphql] Данные авторизации НЕ найдены в scope")
|
|
||||||
|
|
||||||
logger.debug("[graphql] Подготовлен расширенный контекст для запроса")
|
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ from settings import (
|
|||||||
SESSION_TOKEN_HEADER,
|
SESSION_TOKEN_HEADER,
|
||||||
)
|
)
|
||||||
from storage.db import local_session
|
from storage.db import local_session
|
||||||
from storage.redis import redis as redis_adapter
|
|
||||||
from utils.logger import root_logger as logger
|
from utils.logger import root_logger as logger
|
||||||
|
|
||||||
ADMIN_EMAILS = ADMIN_EMAILS_LIST.split(",")
|
ADMIN_EMAILS = ADMIN_EMAILS_LIST.split(",")
|
||||||
@@ -82,7 +81,6 @@ class AuthMiddleware:
|
|||||||
async def authenticate_user(self, token: str) -> tuple[AuthCredentials, AuthenticatedUser | UnauthenticatedUser]:
|
async def authenticate_user(self, token: str) -> tuple[AuthCredentials, AuthenticatedUser | UnauthenticatedUser]:
|
||||||
"""Аутентифицирует пользователя по токену"""
|
"""Аутентифицирует пользователя по токену"""
|
||||||
if not token:
|
if not token:
|
||||||
logger.debug("[auth.authenticate] Токен отсутствует")
|
|
||||||
return AuthCredentials(
|
return AuthCredentials(
|
||||||
author_id=None, scopes={}, logged_in=False, error_message="no token", email=None, token=None
|
author_id=None, scopes={}, logged_in=False, error_message="no token", email=None, token=None
|
||||||
), UnauthenticatedUser()
|
), UnauthenticatedUser()
|
||||||
@@ -202,24 +200,6 @@ class AuthMiddleware:
|
|||||||
scope_headers = scope.get("headers", [])
|
scope_headers = scope.get("headers", [])
|
||||||
if scope_headers:
|
if scope_headers:
|
||||||
headers.update({k.decode("utf-8").lower(): v.decode("utf-8") for k, v in scope_headers})
|
headers.update({k.decode("utf-8").lower(): v.decode("utf-8") for k, v in scope_headers})
|
||||||
logger.debug(f"[middleware] Получены заголовки из scope: {len(headers)}")
|
|
||||||
# Проверяем наличие authorization заголовка
|
|
||||||
if "authorization" in headers:
|
|
||||||
logger.debug(f"[middleware] Authorization заголовок найден: {headers['authorization'][:50]}...")
|
|
||||||
else:
|
|
||||||
logger.debug("[middleware] Authorization заголовок НЕ найден в scope headers")
|
|
||||||
else:
|
|
||||||
logger.debug("[middleware] Заголовки scope отсутствуют")
|
|
||||||
|
|
||||||
# Логируем все заголовки для диагностики
|
|
||||||
logger.debug(f"[middleware] Все заголовки: {list(headers.keys())}")
|
|
||||||
|
|
||||||
# Логируем конкретные заголовки для диагностики
|
|
||||||
auth_header_value = headers.get("authorization", "")
|
|
||||||
logger.debug(f"[middleware] Authorization header: {auth_header_value[:50]}...")
|
|
||||||
|
|
||||||
session_token_value = headers.get(SESSION_TOKEN_HEADER.lower(), "")
|
|
||||||
logger.debug(f"[middleware] {SESSION_TOKEN_HEADER} header: {session_token_value[:50]}...")
|
|
||||||
|
|
||||||
# Используем тот же механизм получения токена, что и в декораторе
|
# Используем тот же механизм получения токена, что и в декораторе
|
||||||
token = None
|
token = None
|
||||||
@@ -227,100 +207,31 @@ class AuthMiddleware:
|
|||||||
# 0. Проверяем сохраненный токен в scope (приоритет)
|
# 0. Проверяем сохраненный токен в scope (приоритет)
|
||||||
if "auth_token" in scope:
|
if "auth_token" in scope:
|
||||||
token = scope["auth_token"]
|
token = scope["auth_token"]
|
||||||
logger.debug(f"[middleware] Токен получен из scope.auth_token: {len(token)}")
|
|
||||||
else:
|
|
||||||
logger.debug("[middleware] scope.auth_token НЕ найден")
|
|
||||||
|
|
||||||
# Стандартная система сессий уже обрабатывает кэширование
|
|
||||||
# Дополнительной проверки Redis кэша не требуется
|
|
||||||
|
|
||||||
# Отладка: детальная информация о запросе без Authorization
|
|
||||||
if not token:
|
|
||||||
# Проверяем, есть ли активные сессии в Redis
|
|
||||||
try:
|
|
||||||
# Получаем все активные сессии
|
|
||||||
session_keys = await redis_adapter.keys("session:*")
|
|
||||||
logger.debug(f"[middleware] Найдено активных сессий в Redis: {len(session_keys)}")
|
|
||||||
|
|
||||||
if session_keys:
|
|
||||||
# Пытаемся найти токен через активные сессии
|
|
||||||
for session_key in session_keys[:3]: # Проверяем первые 3 сессии
|
|
||||||
try:
|
|
||||||
session_data = await redis_adapter.hgetall(session_key)
|
|
||||||
if session_data:
|
|
||||||
logger.debug(f"[middleware] Найдена активная сессия: {session_key}")
|
|
||||||
# Извлекаем user_id из ключа сессии
|
|
||||||
user_id = (
|
|
||||||
session_key.decode("utf-8").split(":")[1]
|
|
||||||
if isinstance(session_key, bytes)
|
|
||||||
else session_key.split(":")[1]
|
|
||||||
)
|
|
||||||
logger.debug(f"[middleware] User ID из сессии: {user_id}")
|
|
||||||
break
|
|
||||||
except Exception as e:
|
|
||||||
logger.debug(f"[middleware] Ошибка чтения сессии {session_key}: {e}")
|
|
||||||
else:
|
|
||||||
logger.debug("[middleware] Активных сессий в Redis не найдено")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.debug(f"[middleware] Ошибка проверки сессий: {e}")
|
|
||||||
|
|
||||||
# 1. Проверяем заголовок Authorization
|
# 1. Проверяем заголовок Authorization
|
||||||
if not token:
|
if not token:
|
||||||
auth_header = headers.get("authorization", "")
|
auth_header = headers.get("authorization", "")
|
||||||
if auth_header:
|
if auth_header:
|
||||||
if auth_header.startswith("Bearer "):
|
token = auth_header[7:].strip() if auth_header.startswith("Bearer ") else auth_header.strip()
|
||||||
token = auth_header[7:].strip()
|
|
||||||
logger.debug(f"[middleware] Токен получен из заголовка Authorization: {len(token)}")
|
|
||||||
else:
|
|
||||||
token = auth_header.strip()
|
|
||||||
logger.debug(f"[middleware] Прямой токен получен из заголовка Authorization: {len(token)}")
|
|
||||||
|
|
||||||
# 2. Проверяем основной заголовок авторизации, если Authorization не найден
|
# 2. Проверяем основной заголовок авторизации, если Authorization не найден
|
||||||
if not token:
|
if not token:
|
||||||
auth_header = headers.get(SESSION_TOKEN_HEADER.lower(), "")
|
auth_header = headers.get(SESSION_TOKEN_HEADER.lower(), "")
|
||||||
if auth_header:
|
if auth_header:
|
||||||
if auth_header.startswith("Bearer "):
|
token = auth_header[7:].strip() if auth_header.startswith("Bearer ") else auth_header.strip()
|
||||||
token = auth_header[7:].strip()
|
|
||||||
logger.debug(f"[middleware] Токен получен из заголовка {SESSION_TOKEN_HEADER}: {len(token)}")
|
|
||||||
else:
|
|
||||||
token = auth_header.strip()
|
|
||||||
logger.debug(f"[middleware] Прямой токен получен из заголовка {SESSION_TOKEN_HEADER}: {len(token)}")
|
|
||||||
|
|
||||||
# 3. Проверяем cookie
|
# 3. Проверяем cookie
|
||||||
if not token:
|
if not token:
|
||||||
cookies = headers.get("cookie", "")
|
cookies = headers.get("cookie", "")
|
||||||
logger.debug(f"[middleware] Проверяем cookies: {cookies[:100]}...")
|
if cookies:
|
||||||
logger.debug(f"[middleware] Ищем cookie с именем: '{SESSION_COOKIE_NAME}'")
|
cookie_items = cookies.split(";")
|
||||||
|
for item in cookie_items:
|
||||||
# 🔍 Диагностика cookies (только для debug уровня)
|
if "=" in item:
|
||||||
if not cookies:
|
name, value = item.split("=", 1)
|
||||||
logger.debug("[middleware] Cookie заголовок отсутствует")
|
cookie_name = name.strip()
|
||||||
logger.debug(f"[middleware] Доступные заголовки: {list(headers.keys())}")
|
if cookie_name == SESSION_COOKIE_NAME:
|
||||||
# 💋 OAuth не использует cookies - это нормальное поведение
|
token = value.strip()
|
||||||
logger.debug("[middleware] OAuth система работает без cookies - токены передаются через заголовки")
|
break
|
||||||
|
|
||||||
cookie_items = cookies.split(";")
|
|
||||||
found_cookies = []
|
|
||||||
for item in cookie_items:
|
|
||||||
if "=" in item:
|
|
||||||
name, value = item.split("=", 1)
|
|
||||||
cookie_name = name.strip()
|
|
||||||
found_cookies.append(cookie_name)
|
|
||||||
if cookie_name == SESSION_COOKIE_NAME:
|
|
||||||
token = value.strip()
|
|
||||||
logger.debug(
|
|
||||||
f"[middleware] ✅ Токен получен из cookie {SESSION_COOKIE_NAME}: {len(token)} символов"
|
|
||||||
)
|
|
||||||
break
|
|
||||||
logger.debug(f"[middleware] Найденные cookies: {found_cookies}")
|
|
||||||
if not token:
|
|
||||||
logger.debug(f"[middleware] ❌ Cookie '{SESSION_COOKIE_NAME}' не найден среди: {found_cookies}")
|
|
||||||
|
|
||||||
if token:
|
|
||||||
logger.debug(f"[middleware] Токен найден: {len(token)} символов")
|
|
||||||
else:
|
|
||||||
logger.debug("[middleware] Токен не найден")
|
|
||||||
|
|
||||||
# Аутентифицируем пользователя
|
# Аутентифицируем пользователя
|
||||||
auth, user = await self.authenticate_user(token or "")
|
auth, user = await self.authenticate_user(token or "")
|
||||||
@@ -332,21 +243,12 @@ class AuthMiddleware:
|
|||||||
# Сохраняем токен в scope для использования в последующих запросах
|
# Сохраняем токен в scope для использования в последующих запросах
|
||||||
if token:
|
if token:
|
||||||
scope["auth_token"] = token
|
scope["auth_token"] = token
|
||||||
logger.debug(f"[middleware] Токен сохранен в scope.auth_token: {len(token)}")
|
|
||||||
logger.debug(f"[middleware] Пользователь аутентифицирован: {user.is_authenticated}")
|
|
||||||
|
|
||||||
# Токен уже сохранен в стандартной системе сессий через SessionTokenManager
|
|
||||||
# Дополнительного кэширования не требуется
|
|
||||||
logger.debug("[middleware] Токен обработан стандартной системой сессий")
|
|
||||||
else:
|
|
||||||
logger.debug("[middleware] Токен не найден, пользователь неаутентифицирован")
|
|
||||||
|
|
||||||
await self.app(scope, receive, send)
|
await self.app(scope, receive, send)
|
||||||
|
|
||||||
def set_context(self, context) -> None:
|
def set_context(self, context) -> None:
|
||||||
"""Сохраняет ссылку на контекст GraphQL запроса"""
|
"""Сохраняет ссылку на контекст GraphQL запроса"""
|
||||||
self._context = context
|
self._context = context
|
||||||
logger.debug(f"[middleware] Установлен контекст GraphQL: {bool(context)}")
|
|
||||||
|
|
||||||
def set_cookie(self, key: str, value: str, **options: Any) -> None:
|
def set_cookie(self, key: str, value: str, **options: Any) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -363,7 +265,6 @@ class AuthMiddleware:
|
|||||||
if self._context and "response" in self._context and hasattr(self._context["response"], "set_cookie"):
|
if self._context and "response" in self._context and hasattr(self._context["response"], "set_cookie"):
|
||||||
try:
|
try:
|
||||||
self._context["response"].set_cookie(key, value, **options)
|
self._context["response"].set_cookie(key, value, **options)
|
||||||
logger.debug(f"[middleware] Установлена cookie {key} через response")
|
|
||||||
success = True
|
success = True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[middleware] Ошибка при установке cookie {key} через response: {e!s}")
|
logger.error(f"[middleware] Ошибка при установке cookie {key} через response: {e!s}")
|
||||||
@@ -372,7 +273,6 @@ class AuthMiddleware:
|
|||||||
if not success and hasattr(self, "_response") and self._response and hasattr(self._response, "set_cookie"):
|
if not success and hasattr(self, "_response") and self._response and hasattr(self._response, "set_cookie"):
|
||||||
try:
|
try:
|
||||||
self._response.set_cookie(key, value, **options)
|
self._response.set_cookie(key, value, **options)
|
||||||
logger.debug(f"[middleware] Установлена cookie {key} через _response")
|
|
||||||
success = True
|
success = True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[middleware] Ошибка при установке cookie {key} через _response: {e!s}")
|
logger.error(f"[middleware] Ошибка при установке cookie {key} через _response: {e!s}")
|
||||||
@@ -390,7 +290,6 @@ class AuthMiddleware:
|
|||||||
if self._context and "response" in self._context and hasattr(self._context["response"], "delete_cookie"):
|
if self._context and "response" in self._context and hasattr(self._context["response"], "delete_cookie"):
|
||||||
try:
|
try:
|
||||||
self._context["response"].delete_cookie(key, **options)
|
self._context["response"].delete_cookie(key, **options)
|
||||||
logger.debug(f"[middleware] Удалена cookie {key} через response")
|
|
||||||
success = True
|
success = True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[middleware] Ошибка при удалении cookie {key} через response: {e!s}")
|
logger.error(f"[middleware] Ошибка при удалении cookie {key} через response: {e!s}")
|
||||||
@@ -399,7 +298,6 @@ class AuthMiddleware:
|
|||||||
if not success and hasattr(self, "_response") and self._response and hasattr(self._response, "delete_cookie"):
|
if not success and hasattr(self, "_response") and self._response and hasattr(self._response, "delete_cookie"):
|
||||||
try:
|
try:
|
||||||
self._response.delete_cookie(key, **options)
|
self._response.delete_cookie(key, **options)
|
||||||
logger.debug(f"[middleware] Удалена cookie {key} через _response")
|
|
||||||
success = True
|
success = True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[middleware] Ошибка при удалении cookie {key} через _response: {e!s}")
|
logger.error(f"[middleware] Ошибка при удалении cookie {key} через _response: {e!s}")
|
||||||
@@ -427,9 +325,6 @@ class AuthMiddleware:
|
|||||||
# Проверяем наличие response в контексте
|
# Проверяем наличие response в контексте
|
||||||
if "response" not in context or not context["response"]:
|
if "response" not in context or not context["response"]:
|
||||||
context["response"] = JSONResponse({})
|
context["response"] = JSONResponse({})
|
||||||
logger.debug("[middleware] Создан новый response объект в контексте GraphQL")
|
|
||||||
|
|
||||||
logger.debug("[middleware] GraphQL resolve: контекст подготовлен, добавлены расширения для работы с cookie")
|
|
||||||
|
|
||||||
return await next_resolver(root, info, *args, **kwargs)
|
return await next_resolver(root, info, *args, **kwargs)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -457,16 +352,8 @@ class AuthMiddleware:
|
|||||||
data = await request.json()
|
data = await request.json()
|
||||||
op_name = data.get("operationName", "").lower()
|
op_name = data.get("operationName", "").lower()
|
||||||
|
|
||||||
# 💋 OAuth НЕ использует cookies - токены передаются только через заголовки/localStorage
|
|
||||||
# Убираем автоматическую установку cookies для login/refreshtoken/getSession
|
|
||||||
if op_name in ["login", "refreshtoken", "getsession"]:
|
|
||||||
logger.debug(f"[graphql_handler] Операция {op_name}: токены передаются БЕЗ cookies")
|
|
||||||
logger.debug(
|
|
||||||
"[graphql_handler] Фронтенд должен извлечь токен из ответа и управлять им самостоятельно"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Если это операция logout, удаляем cookie
|
# Если это операция logout, удаляем cookie
|
||||||
elif op_name == "logout":
|
if op_name == "logout":
|
||||||
response.delete_cookie(
|
response.delete_cookie(
|
||||||
key=SESSION_COOKIE_NAME,
|
key=SESSION_COOKIE_NAME,
|
||||||
secure=SESSION_COOKIE_SECURE,
|
secure=SESSION_COOKIE_SECURE,
|
||||||
@@ -474,9 +361,8 @@ class AuthMiddleware:
|
|||||||
samesite=SESSION_COOKIE_SAMESITE
|
samesite=SESSION_COOKIE_SAMESITE
|
||||||
if SESSION_COOKIE_SAMESITE in ["strict", "lax", "none"]
|
if SESSION_COOKIE_SAMESITE in ["strict", "lax", "none"]
|
||||||
else "none",
|
else "none",
|
||||||
domain=SESSION_COOKIE_DOMAIN, # ✅ КРИТИЧНО: тот же domain что при установке
|
domain=SESSION_COOKIE_DOMAIN,
|
||||||
)
|
)
|
||||||
logger.debug(f"[graphql_handler] Удалена cookie {SESSION_COOKIE_NAME} для операции {op_name}")
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[process_result] Ошибка при обработке POST запроса: {e!s}")
|
logger.error(f"[process_result] Ошибка при обработке POST запроса: {e!s}")
|
||||||
|
|
||||||
|
|||||||
@@ -342,14 +342,14 @@ def get_shouts_with_links(
|
|||||||
if has_field(info, "main_topic") or force_topics:
|
if has_field(info, "main_topic") or force_topics:
|
||||||
main_topic = None
|
main_topic = None
|
||||||
if hasattr(row, "main_topic"):
|
if hasattr(row, "main_topic"):
|
||||||
logger.debug(f"Raw main_topic for shout#{shout_id}: {row.main_topic}")
|
# logger.debug(f"Raw main_topic for shout#{shout_id}: {row.main_topic}")
|
||||||
main_topic = (
|
main_topic = (
|
||||||
orjson.loads(row.main_topic) if isinstance(row.main_topic, str) else row.main_topic
|
orjson.loads(row.main_topic) if isinstance(row.main_topic, str) else row.main_topic
|
||||||
)
|
)
|
||||||
logger.debug(f"Parsed main_topic for shout#{shout_id}: {main_topic}")
|
# logger.debug(f"Parsed main_topic for shout#{shout_id}: {main_topic}")
|
||||||
|
|
||||||
if not main_topic and topics and len(topics) > 0:
|
if not main_topic and topics and len(topics) > 0:
|
||||||
logger.info(f"No main_topic found for shout#{shout_id}, using first topic from list")
|
# logger.info(f"No main_topic found for shout#{shout_id}, using first topic from list")
|
||||||
main_topic = {
|
main_topic = {
|
||||||
"id": topics[0]["id"],
|
"id": topics[0]["id"],
|
||||||
"title": topics[0]["title"],
|
"title": topics[0]["title"],
|
||||||
@@ -365,7 +365,7 @@ def get_shouts_with_links(
|
|||||||
"is_main": True,
|
"is_main": True,
|
||||||
}
|
}
|
||||||
shout_dict["main_topic"] = main_topic
|
shout_dict["main_topic"] = main_topic
|
||||||
logger.debug(f"Final main_topic for shout#{shout_id}: {main_topic}")
|
# logger.debug(f"Final main_topic for shout#{shout_id}: {main_topic}")
|
||||||
|
|
||||||
if has_field(info, "authors") and hasattr(row, "authors"):
|
if has_field(info, "authors") and hasattr(row, "authors"):
|
||||||
authors_data = orjson.loads(row.authors) if isinstance(row.authors, str) else row.authors
|
authors_data = orjson.loads(row.authors) if isinstance(row.authors, str) else row.authors
|
||||||
|
|||||||
Reference in New Issue
Block a user