This commit is contained in:
2025-12-13 14:39:50 +03:00
commit b666cdcb95
79 changed files with 3081 additions and 0 deletions

View File

@@ -0,0 +1,18 @@
from app.middleware.cors import setup_cors
from app.middleware.error_handler import (
validation_exception_handler,
http_exception_handler,
general_exception_handler,
)
from app.middleware.rate_limiter import RateLimitMiddleware
from app.middleware.auth import AuthMiddleware
__all__ = [
"setup_cors",
"validation_exception_handler",
"http_exception_handler",
"general_exception_handler",
"RateLimitMiddleware",
"AuthMiddleware",
]

View File

@@ -0,0 +1,49 @@
from fastapi import Request, HTTPException, status
from starlette.middleware.base import BaseHTTPMiddleware
from app.core.security import decode_token
from app.api.deps import oauth2_scheme
class AuthMiddleware(BaseHTTPMiddleware):
"""Middleware для проверки аутентификации на защищенных маршрутах"""
# Пути, которые не требуют аутентификации
PUBLIC_PATHS = [
"/api/v1/auth/login",
"/api/v1/auth/register",
"/docs",
"/openapi.json",
"/redoc"
]
async def dispatch(self, request: Request, call_next):
# Пропускаем публичные пути
if any(request.url.path.startswith(path) for path in self.PUBLIC_PATHS):
return await call_next(request)
# Проверяем токен для защищенных путей
authorization = request.headers.get("Authorization")
if not authorization:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Not authenticated",
headers={"WWW-Authenticate": "Bearer"},
)
try:
token = authorization.replace("Bearer ", "")
payload = decode_token(token)
if not payload:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid token"
)
except Exception:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid token"
)
response = await call_next(request)
return response

View File

@@ -0,0 +1,15 @@
from fastapi.middleware.cors import CORSMiddleware
from fastapi import FastAPI
from app.core.config import settings
def setup_cors(app: FastAPI):
"""Настройка CORS"""
app.add_middleware(
CORSMiddleware,
allow_origins=settings.CORS_ORIGINS,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

View File

@@ -0,0 +1,36 @@
from fastapi import Request, status
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError
from starlette.exceptions import HTTPException as StarletteHTTPException
import logging
logger = logging.getLogger(__name__)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
"""Обработчик ошибок валидации"""
return JSONResponse(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
content={
"detail": exc.errors(),
"body": exc.body
}
)
async def http_exception_handler(request: Request, exc: StarletteHTTPException):
"""Обработчик HTTP исключений"""
return JSONResponse(
status_code=exc.status_code,
content={"detail": exc.detail}
)
async def general_exception_handler(request: Request, exc: Exception):
"""Обработчик общих исключений"""
logger.error(f"Unhandled exception: {str(exc)}", exc_info=True)
return JSONResponse(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
content={"detail": "Internal server error"}
)

View File

@@ -0,0 +1,35 @@
from fastapi import Request, HTTPException, status
from starlette.middleware.base import BaseHTTPMiddleware
from app.core.config import settings
from app.services.cache_service import cache_service
import time
class RateLimitMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
if not settings.RATE_LIMIT_ENABLED:
return await call_next(request)
# Получаем IP адрес клиента
client_ip = request.client.host if request.client else "unknown"
# Формируем ключ для кэша
cache_key = f"rate_limit:{client_ip}"
# Проверяем количество запросов
current_requests = await cache_service.get(cache_key)
if current_requests:
count = int(current_requests)
if count >= settings.RATE_LIMIT_PER_MINUTE:
raise HTTPException(
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
detail="Rate limit exceeded"
)
await cache_service.set(cache_key, str(count + 1), expire=60)
else:
await cache_service.set(cache_key, "1", expire=60)
response = await call_next(request)
return response