From 4ab2f04de69de67775a0fcf2580472cb91855abe Mon Sep 17 00:00:00 2001 From: Gros Frumos Date: Sat, 21 Mar 2026 07:36:33 +0200 Subject: [PATCH] kin: BATON-SEC-002-backend_dev --- backend/main.py | 4 ++-- backend/middleware.py | 27 ++++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/backend/main.py b/backend/main.py index 38207f0..23970e4 100644 --- a/backend/main.py +++ b/backend/main.py @@ -15,7 +15,7 @@ from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse from backend import config, db, telegram -from backend.middleware import rate_limit_register, verify_admin_token, verify_webhook_secret +from backend.middleware import rate_limit_register, rate_limit_signal, verify_admin_token, verify_webhook_secret from backend.models import ( AdminBlockRequest, AdminCreateUserRequest, @@ -123,7 +123,7 @@ async def register(body: RegisterRequest, _: None = Depends(rate_limit_register) @app.post("/api/signal", response_model=SignalResponse) -async def signal(body: SignalRequest) -> SignalResponse: +async def signal(body: SignalRequest, _: None = Depends(rate_limit_signal)) -> SignalResponse: if await db.is_user_blocked(body.user_id): raise HTTPException(status_code=403, detail="User is blocked") diff --git a/backend/middleware.py b/backend/middleware.py index a384c84..1a3aa39 100644 --- a/backend/middleware.py +++ b/backend/middleware.py @@ -14,6 +14,17 @@ _bearer = HTTPBearer(auto_error=False) _RATE_LIMIT = 5 _RATE_WINDOW = 600 # 10 minutes +_SIGNAL_RATE_LIMIT = 10 +_SIGNAL_RATE_WINDOW = 60 # 1 minute + + +def _get_client_ip(request: Request) -> str: + return ( + request.headers.get("X-Real-IP") + or request.headers.get("X-Forwarded-For", "").split(",")[0].strip() + or (request.client.host if request.client else "unknown") + ) + async def verify_webhook_secret( x_telegram_bot_api_secret_token: str = Header(default=""), @@ -35,7 +46,7 @@ async def verify_admin_token( async def rate_limit_register(request: Request) -> None: counters = request.app.state.rate_counters - client_ip = request.client.host if request.client else "unknown" + client_ip = _get_client_ip(request) now = time.time() count, window_start = counters.get(client_ip, (0, now)) if now - window_start >= _RATE_WINDOW: @@ -45,3 +56,17 @@ async def rate_limit_register(request: Request) -> None: counters[client_ip] = (count, window_start) if count > _RATE_LIMIT: raise HTTPException(status_code=429, detail="Too Many Requests") + + +async def rate_limit_signal(request: Request) -> None: + counters = request.app.state.rate_counters + key = f"sig:{_get_client_ip(request)}" + now = time.time() + count, window_start = counters.get(key, (0, now)) + if now - window_start >= _SIGNAL_RATE_WINDOW: + count = 0 + window_start = now + count += 1 + counters[key] = (count, window_start) + if count > _SIGNAL_RATE_LIMIT: + raise HTTPException(status_code=429, detail="Too Many Requests")