changelog-restored+internal-auth-fix
All checks were successful
Deploy on push / deploy (push) Successful in 6s

This commit is contained in:
2025-06-30 23:10:48 +03:00
parent ab65fd4fd8
commit b01de1fdc1
7 changed files with 322 additions and 128 deletions

View File

@@ -141,29 +141,39 @@ async def validate_graphql_context(info: GraphQLResolveInfo) -> None:
Raises:
GraphQLError: если контекст невалиден или пользователь не авторизован
"""
# Подробное логирование для диагностики
logger.debug("[validate_graphql_context] Начало проверки контекста и авторизации")
# Проверка базовой структуры контекста
if info is None or not hasattr(info, "context"):
logger.error("[decorators] Missing GraphQL context information")
logger.error("[validate_graphql_context] Missing GraphQL context information")
msg = "Internal server error: missing context"
raise GraphQLError(msg)
request = info.context.get("request")
if not request:
logger.error("[decorators] Missing request in context")
logger.error("[validate_graphql_context] Missing request in context")
msg = "Internal server error: missing request"
raise GraphQLError(msg)
# Логируем детали запроса
client_info = {
"ip": getattr(request.client, "host", "unknown") if hasattr(request, "client") else "unknown",
"headers_keys": list(get_safe_headers(request).keys()),
}
logger.debug(f"[validate_graphql_context] Детали запроса: {client_info}")
# Проверяем auth из контекста - если уже авторизован, просто возвращаем
auth = getattr(request, "auth", None)
if auth and auth.logged_in:
logger.debug(f"[decorators] Пользователь уже авторизован: {auth.author_id}")
logger.debug(f"[validate_graphql_context] Пользователь уже авторизован через request.auth: {auth.author_id}")
return
# Если аутентификации нет в request.auth, пробуем получить ее из scope
if hasattr(request, "scope") and "auth" in request.scope:
auth_cred = request.scope.get("auth")
if isinstance(auth_cred, AuthCredentials) and auth_cred.logged_in:
logger.debug(f"[decorators] Пользователь авторизован через scope: {auth_cred.author_id}")
logger.debug(f"[validate_graphql_context] Пользователь авторизован через scope: {auth_cred.author_id}")
# Больше не устанавливаем request.auth напрямую
return
@@ -175,16 +185,22 @@ async def validate_graphql_context(info: GraphQLResolveInfo) -> None:
"ip": getattr(request.client, "host", "unknown") if hasattr(request, "client") else "unknown",
"headers": get_safe_headers(request),
}
logger.warning(f"[decorators] Токен авторизации не найден: {client_info}")
logger.warning(f"[validate_graphql_context] Токен авторизации не найден: {client_info}")
msg = "Unauthorized - please login"
raise GraphQLError(msg)
# Логируем информацию о найденном токене
logger.debug(f"[validate_graphql_context] Токен найден, длина: {len(token)}")
# Используем единый механизм проверки токена из auth.internal
auth_state = await authenticate(request)
logger.debug(
f"[validate_graphql_context] Результат аутентификации: logged_in={auth_state.logged_in}, author_id={auth_state.author_id}, error={auth_state.error}"
)
if not auth_state.logged_in:
error_msg = auth_state.error or "Invalid or expired token"
logger.warning(f"[decorators] Недействительный токен: {error_msg}")
logger.warning(f"[validate_graphql_context] Недействительный токен: {error_msg}")
msg = f"Unauthorized - {error_msg}"
raise GraphQLError(msg)
@@ -192,6 +208,8 @@ async def validate_graphql_context(info: GraphQLResolveInfo) -> None:
with local_session() as session:
try:
author = session.query(Author).filter(Author.id == auth_state.author_id).one()
logger.debug(f"[validate_graphql_context] Найден автор: id={author.id}, email={author.email}")
# Получаем разрешения из ролей
scopes = author.get_permissions()
@@ -209,12 +227,12 @@ async def validate_graphql_context(info: GraphQLResolveInfo) -> None:
if hasattr(request, "scope") and isinstance(request.scope, dict):
request.scope["auth"] = auth_cred
logger.debug(
f"[decorators] Токен успешно проверен и установлен для пользователя {auth_state.author_id}"
f"[validate_graphql_context] Токен успешно проверен и установлен для пользователя {auth_state.author_id}"
)
else:
logger.error("[decorators] Не удалось установить auth: отсутствует request.scope")
logger.error("[validate_graphql_context] Не удалось установить auth: отсутствует request.scope")
except exc.NoResultFound:
logger.error(f"[decorators] Пользователь с ID {auth_state.author_id} не найден в базе данных")
logger.error(f"[validate_graphql_context] Пользователь с ID {auth_state.author_id} не найден в базе данных")
msg = "Unauthorized - user not found"
raise GraphQLError(msg) from None
@@ -244,18 +262,43 @@ def admin_auth_required(resolver: Callable) -> Callable:
@wraps(resolver)
async def wrapper(root: Any = None, info: Optional[GraphQLResolveInfo] = None, **kwargs: dict[str, Any]) -> Any:
try:
# Подробное логирование для диагностики
logger.debug(f"[admin_auth_required] Начало проверки авторизации для {resolver.__name__}")
# Проверяем авторизацию пользователя
if info is None:
logger.error("[admin_auth_required] GraphQL info is None")
msg = "Invalid GraphQL context"
raise GraphQLError(msg)
# Логируем детали запроса
request = info.context.get("request")
client_info = {
"ip": getattr(request.client, "host", "unknown") if hasattr(request, "client") else "unknown",
"headers": {k: v for k, v in get_safe_headers(request).items() if k not in ["authorization", "cookie"]},
}
logger.debug(f"[admin_auth_required] Детали запроса: {client_info}")
# Проверяем наличие токена до validate_graphql_context
token = get_auth_token(request)
logger.debug(f"[admin_auth_required] Токен найден: {bool(token)}, длина: {len(token) if token else 0}")
# Проверяем авторизацию
await validate_graphql_context(info)
logger.debug("[admin_auth_required] validate_graphql_context успешно пройден")
if info:
# Получаем объект авторизации
auth = None
if hasattr(info.context["request"], "scope") and "auth" in info.context["request"].scope:
auth = info.context["request"].scope.get("auth")
logger.debug(f"[admin_auth_required] Auth из scope: {auth.author_id if auth else None}")
elif hasattr(info.context["request"], "auth"):
auth = info.context["request"].auth
logger.debug(f"[admin_auth_required] Auth из request: {auth.author_id if auth else None}")
else:
logger.error("[admin_auth_required] Auth не найден ни в scope, ни в request")
if not auth or not getattr(auth, "logged_in", False):
logger.error("[admin_auth_required] Пользователь не авторизован после validate_graphql_context")
msg = "Unauthorized - please login"
@@ -272,6 +315,7 @@ def admin_auth_required(resolver: Callable) -> Callable:
raise GraphQLError(msg)
author = session.query(Author).filter(Author.id == author_id).one()
logger.debug(f"[admin_auth_required] Найден автор: {author.id}, {author.email}")
# Проверяем, является ли пользователь администратором
if author.email in ADMIN_EMAILS:
@@ -281,6 +325,7 @@ def admin_auth_required(resolver: Callable) -> Callable:
# Проверяем роли пользователя
admin_roles = ["admin", "super"]
user_roles = [role.id for role in author.roles] if author.roles else []
logger.debug(f"[admin_auth_required] Роли пользователя: {user_roles}")
if any(role in admin_roles for role in user_roles):
logger.info(
@@ -303,6 +348,7 @@ def admin_auth_required(resolver: Callable) -> Callable:
if not isinstance(e, GraphQLError):
error_msg = f"Admin access error: {error_msg}"
logger.error(f"Error in admin_auth_required: {error_msg}")
logger.error(f"[admin_auth_required] Ошибка авторизации: {error_msg}")
raise GraphQLError(error_msg) from e
return wrapper

View File

@@ -4,17 +4,15 @@
"""
import time
from typing import Any, Optional
from typing import Optional
from sqlalchemy.orm import exc
from auth.credentials import AuthCredentials
from auth.orm import Author
from auth.state import AuthState
from auth.tokens.storage import TokenStorage as TokenManager
from services.db import local_session
from settings import ADMIN_EMAILS as ADMIN_EMAILS_LIST
from settings import SESSION_COOKIE_NAME, SESSION_TOKEN_HEADER
from utils.logger import root_logger as logger
ADMIN_EMAILS = ADMIN_EMAILS_LIST.split(",")
@@ -90,99 +88,63 @@ async def create_internal_session(author: Author, device_info: Optional[dict] =
)
async def authenticate(request: Any) -> AuthState:
async def authenticate(request) -> AuthState:
"""
Аутентифицирует запрос по токену из разных источников.
Порядок проверки:
1. Проверяет токен в заголовке Authorization
2. Проверяет токен в cookie
Аутентифицирует пользователя по токену из запроса.
Args:
request: Запрос (обычно из middleware)
request: Объект запроса
Returns:
AuthState: Состояние авторизации
AuthState: Состояние аутентификации
"""
state = AuthState()
state.logged_in = False # Изначально считаем, что пользователь не авторизован
token = None
from auth.decorators import get_auth_token
from auth.tokens.sessions import SessionTokenManager
from utils.logger import root_logger as logger
# Проверяем наличие auth в scope (установлено middleware)
if hasattr(request, "scope") and isinstance(request.scope, dict) and "auth" in request.scope:
auth_info = request.scope.get("auth", {})
if isinstance(auth_info, dict) and "token" in auth_info:
token = auth_info["token"]
logger.debug("[auth.authenticate] Извлечен токен из request.scope['auth']")
logger.debug("[authenticate] Начало аутентификации")
# Если токен не найден в scope, проверяем заголовок
# Получаем токен из запроса
token = get_auth_token(request)
if not token:
try:
headers = {}
if hasattr(request, "headers"):
headers = dict(request.headers()) if callable(request.headers) else dict(request.headers)
logger.warning("[authenticate] Токен не найден в запросе")
auth_state = AuthState()
auth_state.logged_in = False
auth_state.author_id = None
auth_state.error = "No authentication token provided"
auth_state.token = None
return auth_state
auth_header = headers.get(SESSION_TOKEN_HEADER, "")
if auth_header and auth_header.startswith("Bearer "):
token = auth_header[7:].strip()
logger.debug(f"[auth.authenticate] Токен получен из заголовка {SESSION_TOKEN_HEADER}")
elif auth_header:
token = auth_header.strip()
logger.debug(f"[auth.authenticate] Прямой токен получен из заголовка {SESSION_TOKEN_HEADER}")
except Exception as e:
logger.error(f"[auth.authenticate] Ошибка при доступе к заголовкам: {e}")
logger.debug(f"[authenticate] Токен найден, длина: {len(token)}")
# Если и в заголовке не найден, проверяем cookie
if not token and hasattr(request, "cookies") and request.cookies:
token = request.cookies.get(SESSION_COOKIE_NAME)
if token:
logger.debug(f"[auth.authenticate] Токен получен из cookie {SESSION_COOKIE_NAME}")
# Проверяем токен
try:
# Создаем экземпляр SessionTokenManager
session_manager = SessionTokenManager()
# Проверяем токен
auth_result = await session_manager.verify_session(token)
# Если токен все еще не найден, возвращаем не авторизованное состояние
if not token:
logger.debug("[auth.authenticate] Токен не найден")
return state
# Проверяем токен через TokenStorage, который теперь совместим с TokenStorage
payload = await TokenManager.verify_session(token)
if not payload:
logger.warning("[auth.authenticate] Токен не валиден: не найдена сессия")
state.error = "Invalid or expired token"
return state
# Создаем успешное состояние авторизации
state.logged_in = True
state.author_id = payload.user_id
state.token = token
state.username = payload.username
# Если запрос имеет атрибут auth, устанавливаем в него авторизационные данные
if hasattr(request, "scope") and isinstance(request.scope, dict):
try:
# Получаем информацию о пользователе для создания AuthCredentials
with local_session() as session:
author = session.query(Author).filter(Author.id == payload.user_id).one_or_none()
if author:
# Получаем разрешения из ролей
scopes = author.get_permissions()
# Создаем объект авторизации
auth_cred = AuthCredentials(
author_id=author.id,
scopes=scopes,
logged_in=True,
email=author.email,
token=token,
error_message="",
)
# Устанавливаем auth в request.scope вместо прямого присваивания к request.auth
request.scope["auth"] = auth_cred
logger.debug(
f"[auth.authenticate] Авторизационные данные установлены в request.scope['auth'] для {payload.user_id}"
)
except Exception as e:
logger.error(f"[auth.authenticate] Ошибка при установке auth в request.scope: {e}")
logger.info(f"[auth.authenticate] Успешная аутентификация пользователя {state.author_id}")
return state
if auth_result and hasattr(auth_result, "user_id"):
logger.debug(f"[authenticate] Успешная аутентификация, user_id: {auth_result.user_id}")
auth_state = AuthState()
auth_state.logged_in = True
auth_state.author_id = auth_result.user_id
auth_state.error = None
auth_state.token = token
return auth_state
error_msg = "Invalid or expired token"
logger.warning(f"[authenticate] Недействительный токен: {error_msg}")
auth_state = AuthState()
auth_state.logged_in = False
auth_state.author_id = None
auth_state.error = error_msg
auth_state.token = None
return auth_state
except Exception as e:
logger.error(f"[authenticate] Ошибка при проверке токена: {e}")
auth_state = AuthState()
auth_state.logged_in = False
auth_state.author_id = None
auth_state.error = f"Authentication error: {e!s}"
auth_state.token = None
return auth_state