wip
This commit is contained in:
210
main.py
210
main.py
@@ -11,9 +11,10 @@ from starlette.middleware.cors import CORSMiddleware
|
||||
from starlette.middleware.authentication import AuthenticationMiddleware
|
||||
from starlette.middleware import Middleware
|
||||
from starlette.requests import Request
|
||||
from starlette.responses import FileResponse, JSONResponse, HTMLResponse, RedirectResponse
|
||||
from starlette.responses import FileResponse, JSONResponse, Response
|
||||
from starlette.routing import Route, Mount
|
||||
from starlette.staticfiles import StaticFiles
|
||||
from starlette.types import ASGIApp
|
||||
|
||||
from cache.precache import precache_data
|
||||
from cache.revalidator import revalidation_manager
|
||||
@@ -22,23 +23,17 @@ from services.redis import redis
|
||||
from services.schema import create_all_tables, resolvers
|
||||
from services.search import search_service
|
||||
|
||||
from settings import DEV_SERVER_PID_FILE_NAME, MODE, ADMIN_EMAILS
|
||||
from utils.logger import root_logger as logger
|
||||
from auth.internal import InternalAuthentication
|
||||
from auth import routes as auth_routes # Импортируем маршруты авторизации
|
||||
from auth.middleware import (
|
||||
AuthorizationMiddleware,
|
||||
GraphQLExtensionsMiddleware,
|
||||
) # Импортируем middleware для авторизации
|
||||
from auth.middleware import AuthMiddleware
|
||||
|
||||
# Импортируем резолверы
|
||||
import_module("resolvers")
|
||||
import_module("auth.resolvers")
|
||||
|
||||
# Создаем схему GraphQL
|
||||
schema = make_executable_schema(load_schema_from_path("schema/"), resolvers)
|
||||
|
||||
# Пути к клиентским файлам
|
||||
CLIENT_DIR = join(os.path.dirname(__file__), "client")
|
||||
DIST_DIR = join(os.path.dirname(__file__), "dist") # Директория для собранных файлов
|
||||
INDEX_HTML = join(os.path.dirname(__file__), "index.html")
|
||||
|
||||
@@ -50,121 +45,35 @@ async def index_handler(request: Request):
|
||||
return FileResponse(INDEX_HTML)
|
||||
|
||||
|
||||
# GraphQL API
|
||||
class CustomGraphQLHTTPHandler(GraphQLHTTPHandler):
|
||||
"""
|
||||
Кастомный GraphQL HTTP обработчик, который добавляет объект response в контекст
|
||||
"""
|
||||
# Создаем единый экземпляр AuthMiddleware для использования с GraphQL
|
||||
auth_middleware = AuthMiddleware(lambda scope, receive, send: None)
|
||||
|
||||
|
||||
class EnhancedGraphQLHTTPHandler(GraphQLHTTPHandler):
|
||||
"""
|
||||
Улучшенный GraphQL HTTP обработчик с поддержкой cookie и авторизации
|
||||
"""
|
||||
|
||||
async def get_context_for_request(self, request: Request, data: dict) -> dict:
|
||||
"""
|
||||
Переопределяем метод для добавления объекта response и extensions в контекст
|
||||
Расширяем контекст для GraphQL запросов
|
||||
"""
|
||||
# Получаем стандартный контекст от базового класса
|
||||
context = await super().get_context_for_request(request, data)
|
||||
# Создаем объект ответа, который будем использовать для установки cookie
|
||||
|
||||
# Добавляем объект ответа для установки cookie
|
||||
response = JSONResponse({})
|
||||
context["response"] = response
|
||||
|
||||
# Добавляем extensions в контекст
|
||||
if "extensions" not in context:
|
||||
context["extensions"] = GraphQLExtensionsMiddleware()
|
||||
|
||||
|
||||
# Интегрируем с AuthMiddleware
|
||||
context["extensions"] = auth_middleware
|
||||
|
||||
return context
|
||||
|
||||
|
||||
graphql_app = GraphQL(schema, debug=MODE == "development", http_handler=CustomGraphQLHTTPHandler())
|
||||
|
||||
|
||||
async def graphql_handler(request):
|
||||
"""Обработчик GraphQL запросов"""
|
||||
# Проверяем заголовок Content-Type
|
||||
content_type = request.headers.get("content-type", "")
|
||||
if not content_type.startswith("application/json") and "application/json" in request.headers.get(
|
||||
"accept", ""
|
||||
):
|
||||
# Если не application/json, но клиент принимает JSON
|
||||
request._headers["content-type"] = "application/json"
|
||||
|
||||
# Обрабатываем GraphQL запрос
|
||||
result = await graphql_app.handle_request(request)
|
||||
|
||||
# Если result - это ответ от сервера, возвращаем его как есть
|
||||
if hasattr(result, "body"):
|
||||
return result
|
||||
|
||||
# Если результат - это словарь, значит нужно его сконвертировать в JSONResponse
|
||||
if isinstance(result, dict):
|
||||
return JSONResponse(result)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
async def admin_handler(request: Request):
|
||||
"""
|
||||
Обработчик для маршрута /admin с серверной проверкой прав доступа
|
||||
"""
|
||||
# Проверяем авторизован ли пользователь
|
||||
if not request.user.is_authenticated:
|
||||
# Если пользователь не авторизован, перенаправляем на главную страницу
|
||||
return RedirectResponse(url="/", status_code=303)
|
||||
|
||||
# Проверяем является ли пользователь администратором
|
||||
auth = getattr(request, "auth", None)
|
||||
is_admin = False
|
||||
|
||||
# Проверяем наличие объекта auth и метода is_admin
|
||||
if auth:
|
||||
try:
|
||||
# Проверяем имеет ли пользователь права администратора
|
||||
is_admin = auth.is_admin
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при проверке прав администратора: {e}")
|
||||
|
||||
# Дополнительная проверка email (для случаев, когда нет метода is_admin)
|
||||
admin_emails = ADMIN_EMAILS.split(",")
|
||||
if not is_admin and hasattr(auth, "email") and auth.email in admin_emails:
|
||||
is_admin = True
|
||||
|
||||
if is_admin:
|
||||
# Если пользователь - администратор, возвращаем HTML-файл
|
||||
return FileResponse(INDEX_HTML)
|
||||
else:
|
||||
# Для авторизованных пользователей без прав администратора показываем страницу с ошибкой доступа
|
||||
return HTMLResponse(
|
||||
"""
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Доступ запрещен</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 0; padding: 0; display: flex; justify-content: center; align-items: center; height: 100vh; background-color: #f5f5f5; }
|
||||
.error-container { max-width: 500px; padding: 30px; background-color: #fff; border-radius: 5px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); text-align: center; }
|
||||
h1 { color: #e74c3c; margin-bottom: 20px; }
|
||||
p { color: #333; margin-bottom: 20px; line-height: 1.5; }
|
||||
.back-button { background-color: #3498db; color: #fff; border: none; padding: 10px 20px; border-radius: 3px; cursor: pointer; text-decoration: none; display: inline-block; }
|
||||
.back-button:hover { background-color: #2980b9; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="error-container">
|
||||
<h1>Доступ запрещен</h1>
|
||||
<p>У вас нет прав для доступа к административной панели. Обратитесь к администратору системы для получения необходимых разрешений.</p>
|
||||
<a href="/" class="back-button">Вернуться на главную</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
""",
|
||||
status_code=403
|
||||
)
|
||||
|
||||
|
||||
# Функция запуска сервера
|
||||
async def start():
|
||||
"""Запуск сервера и инициализация данных"""
|
||||
logger.info(f"Запуск сервера в режиме: {MODE}")
|
||||
|
||||
# Создаем все таблицы в БД
|
||||
create_all_tables()
|
||||
|
||||
@@ -192,47 +101,60 @@ async def shutdown():
|
||||
search_service.close()
|
||||
|
||||
# Удаляем PID-файл, если он существует
|
||||
from settings import DEV_SERVER_PID_FILE_NAME
|
||||
if exists(DEV_SERVER_PID_FILE_NAME):
|
||||
os.unlink(DEV_SERVER_PID_FILE_NAME)
|
||||
|
||||
|
||||
# Добавляем маршруты статических файлов, если директория существует
|
||||
routes = []
|
||||
if exists(DIST_DIR):
|
||||
routes.append(Mount("/", app=StaticFiles(directory=DIST_DIR, html=True)))
|
||||
# Создаем middleware с правильным порядком
|
||||
middleware = [
|
||||
# Начинаем с обработки ошибок
|
||||
Middleware(ExceptionHandlerMiddleware),
|
||||
# CORS должен быть перед другими middleware для корректной обработки preflight-запросов
|
||||
Middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_methods=["GET", "POST", "OPTIONS"], # Явно указываем OPTIONS
|
||||
allow_headers=["*"],
|
||||
allow_credentials=True,
|
||||
),
|
||||
# После CORS идёт обработка авторизации
|
||||
Middleware(AuthMiddleware),
|
||||
# И затем аутентификация
|
||||
Middleware(AuthenticationMiddleware, backend=InternalAuthentication()),
|
||||
]
|
||||
|
||||
|
||||
# Создаем экземпляр GraphQL
|
||||
graphql_app = GraphQL(schema, debug=True)
|
||||
|
||||
|
||||
# Оборачиваем GraphQL-обработчик для лучшей обработки ошибок
|
||||
async def graphql_handler(request: Request):
|
||||
if request.method not in ["GET", "POST", "OPTIONS"]:
|
||||
return JSONResponse({"error": "Method Not Allowed by main.py"}, status_code=405)
|
||||
|
||||
try:
|
||||
result = await graphql_app.handle_request(request)
|
||||
if isinstance(result, Response):
|
||||
return result
|
||||
return JSONResponse(result)
|
||||
except asyncio.CancelledError:
|
||||
return JSONResponse({"error": "Request cancelled"}, status_code=499)
|
||||
except Exception as e:
|
||||
print(f"GraphQL error: {str(e)}")
|
||||
return JSONResponse({"error": str(e)}, status_code=500)
|
||||
|
||||
# Маршруты для API и веб-приложения
|
||||
routes.extend(
|
||||
[
|
||||
Route("/graphql", graphql_handler, methods=["GET", "POST"]),
|
||||
# Добавляем специальный маршрут для админ-панели с проверкой прав доступа
|
||||
Route("/admin", admin_handler, methods=["GET"]),
|
||||
# Маршрут для обработки всех остальных запросов - SPA
|
||||
Route("/{path:path}", index_handler, methods=["GET"]),
|
||||
Route("/", index_handler, methods=["GET"]),
|
||||
]
|
||||
)
|
||||
|
||||
# Добавляем маршруты авторизации
|
||||
routes.extend(auth_routes)
|
||||
# Добавляем маршруты, порядок имеет значение
|
||||
routes = [
|
||||
Route("/graphql", graphql_handler, methods=["GET", "POST", "OPTIONS"]),
|
||||
Mount("/", app=StaticFiles(directory=DIST_DIR, html=True)),
|
||||
]
|
||||
|
||||
# Создаем приложение Starlette с маршрутами и middleware
|
||||
app = Starlette(
|
||||
debug=MODE == "development",
|
||||
routes=routes,
|
||||
middleware=[
|
||||
Middleware(ExceptionHandlerMiddleware),
|
||||
Middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
allow_credentials=True,
|
||||
),
|
||||
# Добавляем middleware для обработки Authorization заголовка с Bearer токеном
|
||||
Middleware(AuthorizationMiddleware),
|
||||
# Добавляем middleware для аутентификации после обработки токенов
|
||||
Middleware(AuthenticationMiddleware, backend=InternalAuthentication()),
|
||||
],
|
||||
middleware=middleware,
|
||||
on_startup=[start],
|
||||
on_shutdown=[shutdown],
|
||||
)
|
||||
|
Reference in New Issue
Block a user