63 lines
1.8 KiB
Python
63 lines
1.8 KiB
Python
from __future__ import annotations
|
|
|
|
from fastapi import HTTPException, Request
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from api.depends import _identity_from_cookie
|
|
|
|
|
|
async def enforce_rate_limit(
|
|
request: Request,
|
|
session: AsyncSession,
|
|
*,
|
|
bucket: str,
|
|
max_per_window: int,
|
|
window_sec: int,
|
|
identity_aware: bool = True,
|
|
) -> None:
|
|
try:
|
|
from api.v2.routes.auth._fallback_limiter import check_and_increment
|
|
from core.redis_cache import cache_incr_checked
|
|
except Exception:
|
|
return
|
|
|
|
owner = "anon"
|
|
if identity_aware:
|
|
try:
|
|
identity = await _identity_from_cookie(session, request)
|
|
if identity is not None and getattr(identity, "id", None):
|
|
owner = f"id:{identity.id}"
|
|
except Exception:
|
|
pass
|
|
|
|
if owner == "anon":
|
|
ip = (request.client.host if request.client else "") or "unknown"
|
|
owner = f"ip:{ip}"
|
|
|
|
key = f"rl:{bucket}:{owner}"
|
|
try:
|
|
count, redis_ok = await cache_incr_checked(key, window_sec)
|
|
if not redis_ok:
|
|
count = check_and_increment(key, max_per_window, window_sec)
|
|
except Exception:
|
|
return
|
|
|
|
if count > max_per_window:
|
|
raise HTTPException(status_code=429, detail="Слишком много запросов, подождите и попробуйте снова")
|
|
|
|
|
|
def rate_limit_dependency(*, bucket: str, max_per_window: int, window_sec: int):
|
|
from api.depends import get_session
|
|
from fastapi import Depends
|
|
|
|
async def _dep(request: Request, session: AsyncSession = Depends(get_session)) -> None:
|
|
await enforce_rate_limit(
|
|
request,
|
|
session,
|
|
bucket=bucket,
|
|
max_per_window=max_per_window,
|
|
window_sec=window_sec,
|
|
)
|
|
|
|
return _dep
|