fix(rate-limit): add NORA_RATE_LIMIT_ENABLED flag and SmartIpKeyExtractor

- Add enabled field to RateLimitConfig (default: true, env: NORA_RATE_LIMIT_ENABLED)
- Skip rate limiter layers entirely when disabled
- Replace PeerIpKeyExtractor with SmartIpKeyExtractor for upload/general routes
  to correctly identify clients behind reverse proxies and Docker bridge networks
- Keep PeerIpKeyExtractor for auth routes (stricter brute-force protection)

Root cause: PeerIpKeyExtractor saw all Docker bridge traffic as single IP (172.17.0.1),
exhausting GCRA bucket for all clients simultaneously. With burst=1M, recovery time
reached 84000+ seconds.
This commit is contained in:
2026-03-03 08:51:33 +00:00
parent fb0f80ac5a
commit 7f8e3cfe68
4 changed files with 57 additions and 36 deletions

View File

@@ -249,6 +249,8 @@ impl Default for AuthConfig {
/// - `NORA_RATE_LIMIT_GENERAL_BURST` - General burst size
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RateLimitConfig {
#[serde(default = "default_rate_limit_enabled")]
pub enabled: bool,
#[serde(default = "default_auth_rps")]
pub auth_rps: u64,
#[serde(default = "default_auth_burst")]
@@ -263,6 +265,9 @@ pub struct RateLimitConfig {
pub general_burst: u32,
}
fn default_rate_limit_enabled() -> bool {
true
}
fn default_auth_rps() -> u64 {
1
}
@@ -285,6 +290,7 @@ fn default_general_burst() -> u32 {
impl Default for RateLimitConfig {
fn default() -> Self {
Self {
enabled: default_rate_limit_enabled(),
auth_rps: default_auth_rps(),
auth_burst: default_auth_burst(),
upload_rps: default_upload_rps(),
@@ -426,6 +432,9 @@ impl Config {
}
// Rate limit config
if let Ok(val) = env::var("NORA_RATE_LIMIT_ENABLED") {
self.rate_limit.enabled = val.to_lowercase() == "true" || val == "1";
}
if let Ok(val) = env::var("NORA_RATE_LIMIT_AUTH_RPS") {
if let Ok(v) = val.parse::<u64>() {
self.rate_limit.auth_rps = v;