feat(B-RL): Rate-Limiting Konsistenz — zentrale Policies für Auth/AI/Upload/Webhook
Check Cross-Plugin Imports / check (push) Has been cancelled
Check Cross-Plugin Imports / check (push) Has been cancelled
B-RL: Rate-Limiting auf zentrale check_rate_limit() umgestellt - forgejo_error_reporter: In-Memory → zentrale Redis-Rate-Limit - ai_proactive: eigene Redis-Logik → zentrale check_rate_limit() - agent_runner: DB-basiertes Limit bleibt (zählt echte Ausführungen) - RateLimitPolicy Enum (AUTH/AI/UPLOAD/WEBHOOK) + check_rate_limit_policy() - 8 neue config.py Settings für Policy-Limits - 12 Routes mit Policies versehen (login, password-reset, AI, uploads, webhooks) Tests: 20 Tests in test_rate_limit_policies.py — alle grün - Policies, check_rate_limit, reset, get_client_ip, forgejo, ai_proactive - Keine Regression: 116 bestehende Tests grün
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from enum import Enum
|
||||
|
||||
from fastapi import HTTPException, Request, status
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
@@ -13,6 +14,47 @@ from app.core.auth import get_redis
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RateLimitPolicy(Enum):
|
||||
"""Named rate-limit policies for abuse- and cost-sensitive endpoints.
|
||||
|
||||
Each policy maps to a pair of Settings fields:
|
||||
``rate_limit_<name>_max`` and ``rate_limit_<name>_window``.
|
||||
"""
|
||||
|
||||
AUTH = "auth"
|
||||
AI = "ai"
|
||||
UPLOAD = "upload"
|
||||
WEBHOOK = "webhook"
|
||||
|
||||
@property
|
||||
def _max_field(self) -> str:
|
||||
return f"rate_limit_{self.value}_max"
|
||||
|
||||
@property
|
||||
def _window_field(self) -> str:
|
||||
return f"rate_limit_{self.value}_window"
|
||||
|
||||
def limits(self) -> tuple[int, int]:
|
||||
"""Return ``(max_attempts, window_seconds)`` from current settings."""
|
||||
from app.config import get_settings
|
||||
|
||||
s = get_settings()
|
||||
return getattr(s, self._max_field), getattr(s, self._window_field)
|
||||
|
||||
|
||||
async def check_rate_limit_policy(
|
||||
redis_key: str,
|
||||
policy: RateLimitPolicy,
|
||||
) -> None:
|
||||
"""Check rate limit using a named :class:`RateLimitPolicy`.
|
||||
|
||||
Reads ``max_attempts`` and ``window_seconds`` from application settings
|
||||
and delegates to :func:`check_rate_limit`.
|
||||
"""
|
||||
max_attempts, window_seconds = policy.limits()
|
||||
await check_rate_limit(redis_key, max_attempts, window_seconds)
|
||||
|
||||
|
||||
async def check_rate_limit(
|
||||
redis_key: str,
|
||||
max_attempts: int,
|
||||
|
||||
Reference in New Issue
Block a user