""" Утилиты для аутентификации и авторизации. Автор: Сергей Антропов Сайт: https://devops.org.ru """ import logging from typing import Optional, Callable, Any from functools import wraps from fastapi import HTTPException, Security, Depends, Request from fastapi.security import APIKeyHeader from starlette.requests import Request as StarletteRequest from app.core.config import get_settings logger = logging.getLogger(__name__) # Request может быть как из FastAPI, так и из Starlette # Оба типа совместимы, поэтому используем StarletteRequest как базовый тип RequestType = StarletteRequest # Схема безопасности для API ключа api_key_header = APIKeyHeader( name="X-API-Key", auto_error=False, description="API ключ для авторизации" ) def verify_api_key(api_key: Optional[str] = Security(api_key_header)) -> bool: """ Проверить API ключ для авторизации (обязательная авторизация). Используется как зависимость FastAPI (Depends) для отображения в Swagger UI. Также помечает контекст запроса, что API ключ был проверен. Args: api_key: API ключ из заголовка X-API-Key. Returns: True если API ключ верный. Raises: HTTPException: Если API ключ неверный или не указан. """ settings = get_settings() # Если API ключ не установлен в настройках, доступ запрещен if not settings.api_key: logger.warning("API_KEY не установлен - доступ запрещен") raise HTTPException( status_code=401, detail="API ключ не настроен на сервере" ) # Если API ключ не передан, доступ запрещен if not api_key: raise HTTPException( status_code=401, detail="Неверный или отсутствующий API ключ", headers={"WWW-Authenticate": "ApiKey"} ) # Проверяем API ключ if api_key != settings.api_key: logger.warning(f"Неверный API ключ: {api_key[:10]}...") raise HTTPException( status_code=401, detail="Неверный или отсутствующий API ключ", headers={"WWW-Authenticate": "ApiKey"} ) # Помечаем, что API ключ был проверен через dependency # Это позволяет декоратору @require_api_key не выполнять повторную проверку try: from starlette.context import contextvars # Используем contextvars для хранения информации о проверке # Но это может не работать во всех случаях pass except ImportError: pass return True def verify_api_key_optional(api_key: Optional[str] = Security(api_key_header)) -> Optional[bool]: """ Проверить API ключ для авторизации (опциональная авторизация). Используется как зависимость FastAPI (Depends). Args: api_key: API ключ из заголовка X-API-Key. Returns: True если API ключ верный, None если не передан, выбрасывает исключение если неверный. Raises: HTTPException: Если API ключ неверный. """ settings = get_settings() # Если API ключ не установлен в настройках, возвращаем None (нет авторизации) if not settings.api_key: return None # Если API ключ не передан, возвращаем None (нет авторизации) if not api_key: return None # Проверяем API ключ if api_key != settings.api_key: logger.warning(f"Неверный API ключ: {api_key[:10]}...") raise HTTPException( status_code=401, detail="Неверный или отсутствующий API ключ", headers={"WWW-Authenticate": "ApiKey"} ) return True # Удобные константы для использования в endpoints (через dependencies) require_api_key_dependency = Depends(verify_api_key) require_api_key_optional = Depends(verify_api_key_optional) def require_api_key(func: Callable) -> Callable: """ Декоратор для пометки функции как требующей API ключ. Использование: @require_api_key @router.post("/endpoint", dependencies=[require_api_key_dependency]) async def my_endpoint(request: Request, ...): ... Примечание: Декоратор используется только для пометки функции. Фактическая проверка API ключа выполняется через `dependencies=[require_api_key_dependency]`, который также обеспечивает отображение замочка в Swagger UI. Декоратор не выполняет проверку API ключа - это делает dependency. Декоратор оставлен для удобства и возможного расширения в будущем. Args: func: Функция для декорирования. Returns: Декорированная функция с пометкой о необходимости API ключа. """ # Помечаем функцию, что она требует API ключ func.__requires_api_key__ = True # Просто возвращаем функцию без изменений # Проверка API ключа выполняется через dependency return func def hide_from_api(func: Callable) -> Callable: """ Декоратор для скрытия эндпоинта из API документации (Swagger UI). Использование: @hide_from_api @router.post("/debug/dump") async def debug_endpoint(...): ... Примечание: Декоратор помечает функцию как скрытую от API. Эндпоинт все еще будет работать, но не будет отображаться в Swagger UI. Декоратор должен быть применен ПЕРЕД декоратором route (снизу вверх). Args: func: Функция для декорирования. Returns: Декорированная функция с пометкой о скрытии от API. """ # Помечаем функцию, что она должна быть скрыта от API func.__hide_from_api__ = True # Просто возвращаем функцию без изменений # Скрытие из API выполняется в custom_openapi return func