use std::collections::HashMap; use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; /// Thread-safe sliding-window rate limiter backed by an in-memory HashMap. /// Each key (e.g. `"join:{ip}"` or `"upload:{user_id}"`) tracks timestamps /// of recent requests and rejects new ones once the window is full. #[derive(Clone)] pub struct RateLimiter { windows: Arc>>>, } impl RateLimiter { pub fn new() -> Self { Self { windows: Arc::new(Mutex::new(HashMap::new())), } } /// Returns `true` if the request is allowed, `false` if rate-limited. pub fn check(&self, key: impl Into, max: usize, window: Duration) -> bool { let now = Instant::now(); let key = key.into(); let mut map = self.windows.lock().unwrap(); let timestamps = map.entry(key).or_default(); // Drop entries outside the window timestamps.retain(|&t| now.duration_since(t) < window); if timestamps.len() < max { timestamps.push(now); true } else { false } } } /// Extract the client IP from X-Forwarded-For (Caddy sets this) or fall back /// to a provided socket address string. pub fn client_ip(headers: &axum::http::HeaderMap, fallback: &str) -> String { headers .get("x-forwarded-for") .and_then(|v| v.to_str().ok()) .and_then(|s| s.split(',').next()) .map(|s| s.trim().to_owned()) .unwrap_or_else(|| fallback.to_owned()) }