diff --git a/src/api.rs b/src/api.rs index 87c2e8d..57430c1 100644 --- a/src/api.rs +++ b/src/api.rs @@ -17,7 +17,9 @@ use axum::{ use metrics_exporter_prometheus::{Matcher, PrometheusBuilder}; use serde_json::json; use std::{sync::Arc, time::Instant}; -use tower_governor::{governor::GovernorConfigBuilder, GovernorLayer}; +use tower_governor::{ + governor::GovernorConfigBuilder, key_extractor::SmartIpKeyExtractor, GovernorLayer, +}; use tower_http::{ cors::CorsLayer, services::{ServeDir, ServeFile}, @@ -111,8 +113,15 @@ pub fn create_router(state: ApiState, frontend_dir: &str) -> Router { let static_path = std::path::PathBuf::from(frontend_dir); let index_path = static_path.join("index.html"); + // SmartIpKeyExtractor читает x-forwarded-for/x-real-ip/forwarded (в этом + // порядке), падая на peer IP только если их нет. Без этого — дефолтный + // PeerIpKeyExtractor видел бы просто 127.0.0.1 (nginx проксирует локально) + // ОДИН И ТОТ ЖЕ для абсолютно всех посетителей сразу, и лимит превращался + // бы в глобальный на весь сервис, а не per-visitor (см. incident: 3 попытки + // логина от кого угодно — и /auth 429 для всех остальных до истечения окна). let gov_conf = Arc::new( GovernorConfigBuilder::default() + .key_extractor(SmartIpKeyExtractor) .per_millisecond(100) .burst_size(10) .finish() @@ -121,6 +130,7 @@ pub fn create_router(state: ApiState, frontend_dir: &str) -> Router { let seed_limit_conf = Arc::new( GovernorConfigBuilder::default() + .key_extractor(SmartIpKeyExtractor) .per_millisecond(5000) .burst_size(3) .finish()