middlware-fix
All checks were successful
Deploy on push / deploy (push) Successful in 5s

This commit is contained in:
Untone 2025-06-28 13:56:05 +03:00
parent c48f5f9368
commit 52bf78320b
5 changed files with 120 additions and 94 deletions

View File

@ -82,16 +82,23 @@ class AuthMiddleware:
async def authenticate_user(self, token: str) -> tuple[AuthCredentials, AuthenticatedUser | UnauthenticatedUser]:
"""Аутентифицирует пользователя по токену"""
if not token:
logger.debug("[auth.authenticate] Токен отсутствует")
return AuthCredentials(
author_id=None, scopes={}, logged_in=False, error_message="no token", email=None, token=None
), UnauthenticatedUser()
# Проверяем сессию в Redis
try:
payload = await TokenManager.verify_session(token)
if not payload:
logger.debug("[auth.authenticate] Недействительный токен")
logger.debug("[auth.authenticate] Недействительный токен или сессия не найдена")
return AuthCredentials(
author_id=None, scopes={}, logged_in=False, error_message="Invalid token", email=None, token=None
author_id=None,
scopes={},
logged_in=False,
error_message="Invalid token or session",
email=None,
token=None,
), UnauthenticatedUser()
with local_session() as session:
@ -141,9 +148,24 @@ class AuthMiddleware:
return credentials, user
except exc.NoResultFound:
logger.debug("[auth.authenticate] Пользователь не найден")
logger.debug("[auth.authenticate] Пользователь не найден в базе данных")
return AuthCredentials(
author_id=None, scopes={}, logged_in=False, error_message="User not found", email=None, token=None
author_id=None,
scopes={},
logged_in=False,
error_message="User not found",
email=None,
token=None,
), UnauthenticatedUser()
except Exception as e:
logger.error(f"[auth.authenticate] Ошибка при работе с базой данных: {e}")
return AuthCredentials(
author_id=None, scopes={}, logged_in=False, error_message=str(e), email=None, token=None
), UnauthenticatedUser()
except Exception as e:
logger.error(f"[auth.authenticate] Ошибка при проверке сессии: {e}")
return AuthCredentials(
author_id=None, scopes={}, logged_in=False, error_message=str(e), email=None, token=None
), UnauthenticatedUser()
async def __call__(

View File

@ -15,7 +15,15 @@ from auth.tokens.storage import TokenStorage
from resolvers.auth import generate_unique_slug
from services.db import local_session
from services.redis import redis
from settings import FRONTEND_URL, OAUTH_CLIENTS
from settings import (
FRONTEND_URL,
OAUTH_CLIENTS,
SESSION_COOKIE_HTTPONLY,
SESSION_COOKIE_MAX_AGE,
SESSION_COOKIE_NAME,
SESSION_COOKIE_SAMESITE,
SESSION_COOKIE_SECURE,
)
from utils.logger import root_logger as logger
# Type для dependency injection сессии
@ -302,7 +310,10 @@ async def oauth_login(_: None, _info: GraphQLResolveInfo, provider: str, callbac
async def oauth_callback(request: Any) -> JSONResponse | RedirectResponse:
"""Обрабатывает callback от OAuth провайдера"""
"""
Обработчик OAuth callback.
Создает или обновляет пользователя и устанавливает сессионный токен.
"""
try:
# Получаем state из query параметров
state = request.query_params.get("state")
@ -341,12 +352,12 @@ async def oauth_callback(request: Any) -> JSONResponse | RedirectResponse:
redirect_url = f"{stored_redirect_uri}?state={state}&access_token={session_token}"
response = RedirectResponse(url=redirect_url)
response.set_cookie(
"session_token",
SESSION_COOKIE_NAME,
session_token,
httponly=True,
secure=True,
samesite="lax",
max_age=30 * 24 * 60 * 60, # 30 days
httponly=SESSION_COOKIE_HTTPONLY,
secure=SESSION_COOKIE_SECURE,
samesite=SESSION_COOKIE_SAMESITE,
max_age=SESSION_COOKIE_MAX_AGE,
)
return response
@ -460,12 +471,12 @@ async def oauth_callback_http(request: Request) -> JSONResponse | RedirectRespon
# Возвращаем redirect с cookie
response = RedirectResponse(url="/auth/success", status_code=307)
response.set_cookie(
"session_token",
SESSION_COOKIE_NAME,
session_token,
httponly=True,
secure=True,
samesite="lax",
max_age=30 * 24 * 60 * 60, # 30 дней
httponly=SESSION_COOKIE_HTTPONLY,
secure=SESSION_COOKIE_SECURE,
samesite=SESSION_COOKIE_SAMESITE,
max_age=SESSION_COOKIE_MAX_AGE,
)
return response

View File

@ -230,6 +230,10 @@ class SessionTokenManager(BaseTokenManager):
"""
Проверяет сессию по токену для совместимости с TokenStorage
"""
if not token:
logger.debug("Пустой токен")
return None
logger.debug(f"Проверка сессии для токена: {token[:20]}...")
# Декодируем токен для получения payload
@ -239,15 +243,23 @@ class SessionTokenManager(BaseTokenManager):
logger.error("Не удалось декодировать токен")
return None
if not hasattr(payload, "user_id"):
logger.error("В токене отсутствует user_id")
return None
logger.debug(f"Успешно декодирован токен, user_id={payload.user_id}")
except Exception as e:
logger.error(f"Ошибка при декодировании токена: {e}")
return None
# Проверяем валидность токена
valid, _ = await self.validate_session_token(token)
try:
valid, error = await self.validate_session_token(token)
if valid:
logger.debug(f"Сессия найдена для пользователя {payload.user_id}")
return payload
logger.warning(f"Сессия не найдена: {payload.user_id}")
logger.warning(f"Сессия не найдена: {payload.user_id}, ошибка: {error}")
return None
except Exception as e:
logger.error(f"Ошибка при валидации сессии: {e}")
return None

33
main.py
View File

@ -38,28 +38,29 @@ schema = make_executable_schema(load_schema_from_path("schema/"), list(resolvers
# Создаем middleware с правильным порядком
middleware = [
# Начинаем с обработки ошибок
Middleware(ExceptionHandlerMiddleware),
# CORS должен быть перед другими middleware для корректной обработки preflight-запросов
Middleware(
CORSMiddleware,
allow_origins=[
"https://localhost:3000",
"http://localhost:3000",
"https://testing.discours.io",
"https://testing.dscrs.site",
"https://testing3.discours.io",
"https://v3.dscrs.site",
"https://session-daily.vercel.app",
"https://coretest.discours.io",
"https://core.discours.io",
"https://discours.io",
"https://new.discours.io",
],
allow_methods=["GET", "POST", "OPTIONS"], # Явно указываем OPTIONS
allow_headers=["*"],
allow_credentials=True,
),
# извлечение токена + аутентификация + cookies
# Аутентификация должна быть после CORS
Middleware(AuthMiddleware),
]
# Создаем экземпляр GraphQL с улучшенным обработчиком
graphql_app = GraphQL(schema, debug=DEVMODE, http_handler=EnhancedGraphQLHTTPHandler())
@ -224,26 +225,6 @@ async def lifespan(app: Starlette):
print("[lifespan] Shutdown complete")
middleware = [
# Начинаем с обработки ошибок
Middleware(ExceptionHandlerMiddleware),
# CORS должен быть перед другими middleware для корректной обработки preflight-запросов
Middleware(
CORSMiddleware,
allow_origins=[
"https://localhost:3000",
"http://localhost:3000",
"https://testing.discours.io",
"https://testing3.discours.io",
"https://coretest.discours.io",
"https://session-daily.vercel.app",
],
allow_methods=["GET", "POST", "OPTIONS"], # Явно указываем OPTIONS
allow_headers=["*"],
allow_credentials=True,
),
]
# Обновляем маршрут в Starlette
app = Starlette(
routes=[
@ -253,7 +234,7 @@ app = Starlette(
Route("/oauth/{provider}/callback", oauth_callback, methods=["GET"]),
Mount("/", app=StaticFiles(directory=str(DIST_DIR), html=True)),
],
middleware=middleware,
middleware=middleware, # Используем единый список middleware
lifespan=lifespan,
debug=True,
)

View File

@ -63,7 +63,7 @@ JWT_REFRESH_TOKEN_EXPIRE_DAYS = 30
# Настройки для HTTP cookies (используется в auth middleware)
SESSION_COOKIE_NAME = "auth_token"
SESSION_COOKIE_SECURE = False
SESSION_COOKIE_SECURE = True # Включаем для HTTPS
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE: Literal["lax", "strict", "none"] = "lax"
SESSION_COOKIE_MAX_AGE = 30 * 24 * 60 * 60 # 30 дней