From 90260534ebd4e2046c770b70d06aaccebf89da11 Mon Sep 17 00:00:00 2001 From: Untone Date: Fri, 30 May 2025 14:08:29 +0300 Subject: [PATCH] sigil-on --- auth/middleware.py | 14 +++++++----- auth/oauth.py | 11 +++++----- cache/cache.py | 28 ++++++++++++++---------- main.py | 2 +- nginx.conf.sigil.off => nginx.conf.sigil | 0 resolvers/stat.py | 8 +++---- 6 files changed, 36 insertions(+), 27 deletions(-) rename nginx.conf.sigil.off => nginx.conf.sigil (100%) diff --git a/auth/middleware.py b/auth/middleware.py index 3b84600c..e5335dc6 100644 --- a/auth/middleware.py +++ b/auth/middleware.py @@ -5,12 +5,12 @@ import time from typing import Any, Dict +from sqlalchemy.orm import exc from starlette.authentication import UnauthenticatedUser from starlette.datastructures import Headers from starlette.requests import Request from starlette.responses import JSONResponse, Response from starlette.types import ASGIApp, Receive, Scope, Send -from sqlalchemy.orm import exc from auth.credentials import AuthCredentials from auth.orm import Author @@ -18,6 +18,8 @@ from auth.sessions import SessionManager from services.db import local_session from settings import ( ADMIN_EMAILS as ADMIN_EMAILS_LIST, +) +from settings import ( SESSION_COOKIE_HTTPONLY, SESSION_COOKIE_MAX_AGE, SESSION_COOKIE_NAME, @@ -33,7 +35,9 @@ ADMIN_EMAILS = ADMIN_EMAILS_LIST.split(",") class AuthenticatedUser: """Аутентифицированный пользователь""" - def __init__(self, user_id: str, username: str = "", roles: list = None, permissions: dict = None, token: str = None): + def __init__( + self, user_id: str, username: str = "", roles: list = None, permissions: dict = None, token: str = None + ): self.user_id = user_id self.username = username self.roles = roles or [] @@ -177,11 +181,11 @@ class AuthMiddleware: # Аутентифицируем пользователя auth, user = await self.authenticate_user(token) - + # Добавляем в scope данные авторизации и пользователя scope["auth"] = auth scope["user"] = user - + if token: # Обновляем заголовки в scope для совместимости new_headers = [] @@ -190,7 +194,7 @@ class AuthMiddleware: new_headers.append((name, value)) new_headers.append((SESSION_TOKEN_HEADER.encode("latin1"), token.encode("latin1"))) scope["headers"] = new_headers - + logger.debug(f"[middleware] Пользователь аутентифицирован: {user.is_authenticated}") else: logger.debug(f"[middleware] Токен не найден, пользователь неаутентифицирован") diff --git a/auth/oauth.py b/auth/oauth.py index e85480cf..c627663a 100644 --- a/auth/oauth.py +++ b/auth/oauth.py @@ -1,18 +1,18 @@ import time -import orjson from secrets import token_urlsafe +import orjson from authlib.integrations.starlette_client import OAuth from authlib.oauth2.rfc7636 import create_s256_code_challenge from starlette.responses import JSONResponse, RedirectResponse from auth.orm import Author from auth.tokenstorage 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 utils.logger import root_logger as logger -from resolvers.auth import generate_unique_slug oauth = OAuth() @@ -100,7 +100,7 @@ async def oauth_login(request): # Получаем параметры из query string state = request.query_params.get("state") redirect_uri = request.query_params.get("redirect_uri", FRONTEND_URL) - + if not state: return JSONResponse({"error": "State parameter is required"}, status_code=400) @@ -113,7 +113,7 @@ async def oauth_login(request): "code_verifier": code_verifier, "provider": provider, "redirect_uri": redirect_uri, - "created_at": int(time.time()) + "created_at": int(time.time()), } await store_oauth_state(state, oauth_data) @@ -172,7 +172,7 @@ async def oauth_callback(request): if not author: # Генерируем slug из имени или email slug = generate_unique_slug(profile["name"] or profile["email"].split("@")[0]) - + author = Author( email=profile["email"], name=profile["name"], @@ -223,6 +223,7 @@ async def store_oauth_state(state: str, data: dict) -> None: key = f"oauth_state:{state}" await redis.execute("SETEX", key, OAUTH_STATE_TTL, orjson.dumps(data)) + async def get_oauth_state(state: str) -> dict: """Получает и удаляет OAuth состояние из Redis (one-time use)""" key = f"oauth_state:{state}" diff --git a/cache/cache.py b/cache/cache.py index b2411074..b8f9b3dd 100644 --- a/cache/cache.py +++ b/cache/cache.py @@ -109,37 +109,41 @@ async def update_follower_stat(follower_id, entity_type, count): # Get author from cache async def get_cached_author(author_id: int, get_with_stat): logger.debug(f"[get_cached_author] Начало выполнения для author_id: {author_id}") - + author_key = f"author:id:{author_id}" logger.debug(f"[get_cached_author] Проверка кэша по ключу: {author_key}") - + result = await redis.execute("GET", author_key) if result: logger.debug(f"[get_cached_author] Найдены данные в кэше, размер: {len(result)} байт") cached_data = orjson.loads(result) - logger.debug(f"[get_cached_author] Кэшированные данные имеют ключи: {list(cached_data.keys()) if cached_data else 'None'}") + logger.debug( + f"[get_cached_author] Кэшированные данные имеют ключи: {list(cached_data.keys()) if cached_data else 'None'}" + ) return cached_data - + logger.debug(f"[get_cached_author] Данные не найдены в кэше, загрузка из БД") - + # Load from database if not found in cache q = select(Author).where(Author.id == author_id) authors = get_with_stat(q) logger.debug(f"[get_cached_author] Результат запроса из БД: {len(authors) if authors else 0} записей") - + if authors: author = authors[0] logger.debug(f"[get_cached_author] Получен автор из БД: {type(author)}, id: {getattr(author, 'id', 'N/A')}") - + # Используем безопасный вызов dict() для Author - author_dict = author.dict() if hasattr(author, 'dict') else author.__dict__ - logger.debug(f"[get_cached_author] Сериализованные данные автора: {list(author_dict.keys()) if author_dict else 'None'}") - + author_dict = author.dict() if hasattr(author, "dict") else author.__dict__ + logger.debug( + f"[get_cached_author] Сериализованные данные автора: {list(author_dict.keys()) if author_dict else 'None'}" + ) + await cache_author(author_dict) logger.debug(f"[get_cached_author] Автор кэширован") - + return author_dict - + logger.warning(f"[get_cached_author] Автор с ID {author_id} не найден в БД") return None diff --git a/main.py b/main.py index ae502126..80e83f44 100644 --- a/main.py +++ b/main.py @@ -15,6 +15,7 @@ from starlette.staticfiles import StaticFiles from auth.handler import EnhancedGraphQLHTTPHandler from auth.middleware import AuthMiddleware, auth_middleware +from auth.oauth import oauth_callback, oauth_login from cache.precache import precache_data from cache.revalidator import revalidation_manager from services.exception import ExceptionHandlerMiddleware @@ -24,7 +25,6 @@ from services.search import check_search_service, initialize_search_index_backgr from services.viewed import ViewedStorage from settings import DEV_SERVER_PID_FILE_NAME from utils.logger import root_logger as logger -from auth.oauth import oauth_login, oauth_callback DEVMODE = os.getenv("DOKKU_APP_TYPE", "false").lower() == "false" DIST_DIR = join(os.path.dirname(__file__), "dist") # Директория для собранных файлов diff --git a/nginx.conf.sigil.off b/nginx.conf.sigil similarity index 100% rename from nginx.conf.sigil.off rename to nginx.conf.sigil diff --git a/resolvers/stat.py b/resolvers/stat.py index 6014627d..f7173a9c 100644 --- a/resolvers/stat.py +++ b/resolvers/stat.py @@ -290,26 +290,26 @@ def get_with_stat(q): stat["followers"] = cols[2] # Статистика по подписчикам if is_author: # Дополнительная проверка типа entity.id - if not hasattr(entity, 'id'): + if not hasattr(entity, "id"): logger.error(f"Entity does not have id attribute: {entity}") continue entity_id = entity.id if not isinstance(entity_id, int): logger.error(f"Entity id is not integer: {entity_id} (type: {type(entity_id)})") continue - + stat["authors"] = get_author_authors_stat(entity_id) # Статистика по подпискам на авторов stat["comments"] = get_author_comments_stat(entity_id) # Статистика по комментариям else: # Дополнительная проверка типа entity.id для тем - if not hasattr(entity, 'id'): + if not hasattr(entity, "id"): logger.error(f"Entity does not have id attribute: {entity}") continue entity_id = entity.id if not isinstance(entity_id, int): logger.error(f"Entity id is not integer: {entity_id} (type: {type(entity_id)})") continue - + stat["authors"] = get_topic_authors_stat(entity_id) # Статистика по авторам темы entity.stat = stat records.append(entity)