1mod account;
25mod client_ip;
26#[cfg(test)]
27mod tests;
28
29use std::net::{IpAddr, SocketAddr};
30use std::sync::Arc;
31use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
32use std::time::{SystemTime, UNIX_EPOCH};
33
34use axum::Json;
35use axum::body::{Body, to_bytes};
36use axum::extract::{ConnectInfo, Request};
37use axum::http::{HeaderMap, HeaderValue, StatusCode};
38use axum::middleware::Next;
39use axum::response::{IntoResponse, Response};
40use dashmap::DashMap;
41use serde_json::json;
42use uuid::Uuid;
43
44use ironflow_auth::extractor::{API_KEY_PREFIX, API_KEY_SUFFIX_LEN};
45use ironflow_auth::jwt::{AccessToken, JwtConfig};
46use ironflow_store::store::Store;
47
48use self::account::{ACCOUNT_BODY_LIMIT, account_key};
49use self::client_ip::client_ip;
50
51pub use self::account::AccountLimit;
52pub use self::client_ip::{InvalidTrustedProxy, TrustedProxies};
53
54const SWEEP_THRESHOLD: usize = 10_000;
57
58#[derive(Debug, Clone, PartialEq, Eq, Hash)]
73pub enum RateLimitKey {
74 ApiKey(Uuid),
76 User(Uuid),
78 Ip(IpAddr),
80 Account(String),
82}
83
84struct WindowEntry {
85 count: AtomicU32,
86 window_start: AtomicU64,
87}
88
89#[derive(Clone)]
99pub struct RateLimitState {
100 counters: Arc<DashMap<RateLimitKey, WindowEntry>>,
101 burst: u32,
102}
103
104#[derive(Clone)]
134pub struct RateLimitContext {
135 pub store: Arc<dyn Store>,
137 pub jwt_config: Arc<JwtConfig>,
139 pub limiter: RateLimitState,
141 pub trusted_proxies: TrustedProxies,
143 pub account_limit: AccountLimit,
145}
146
147struct RateLimitResult {
148 limit: u32,
149 remaining: u32,
150 reset: u64,
151 allowed: bool,
152}
153
154pub fn per_minute(requests_per_minute: u32) -> RateLimitState {
170 assert!(requests_per_minute > 0, "burst must be > 0");
171 RateLimitState {
172 counters: Arc::new(DashMap::new()),
173 burst: requests_per_minute,
174 }
175}
176
177const WINDOW_SECS: u64 = 60;
178
179fn now_epoch() -> u64 {
180 SystemTime::now()
181 .duration_since(UNIX_EPOCH)
182 .expect("system clock before UNIX epoch")
183 .as_secs()
184}
185
186fn check_rate_limit(limiter: &RateLimitState, key: RateLimitKey, limit: u32) -> RateLimitResult {
187 let now = now_epoch();
188
189 if limiter.counters.len() >= SWEEP_THRESHOLD && !limiter.counters.contains_key(&key) {
190 limiter
191 .counters
192 .retain(|_, entry| entry.window_start.load(Ordering::Acquire) + WINDOW_SECS > now);
193 }
194
195 let entry = limiter.counters.entry(key).or_insert_with(|| WindowEntry {
196 count: AtomicU32::new(0),
197 window_start: AtomicU64::new(now),
198 });
199
200 let window_start = entry.window_start.load(Ordering::Acquire);
201 if now >= window_start + WINDOW_SECS
202 && entry
203 .window_start
204 .compare_exchange(window_start, now, Ordering::AcqRel, Ordering::Relaxed)
205 .is_ok()
206 {
207 entry.count.store(0, Ordering::Release);
208 }
209
210 let count = entry.count.fetch_add(1, Ordering::AcqRel) + 1;
211 let reset = entry.window_start.load(Ordering::Acquire) + WINDOW_SECS;
212
213 if count > limit {
214 entry.count.fetch_sub(1, Ordering::AcqRel);
215 RateLimitResult {
216 limit,
217 remaining: 0,
218 reset,
219 allowed: false,
220 }
221 } else {
222 RateLimitResult {
223 limit,
224 remaining: limit.saturating_sub(count),
225 reset,
226 allowed: true,
227 }
228 }
229}
230
231pub async fn rate_limit(mut req: Request, next: Next) -> Response {
244 let ctx = req.extensions_mut().remove::<RateLimitContext>();
245 let Some(ctx) = ctx else {
246 return next.run(req).await;
247 };
248 let peer = req
249 .extensions()
250 .get::<ConnectInfo<SocketAddr>>()
251 .map(|ConnectInfo(addr)| addr.ip());
252 let (key, override_limit) = extract_rate_limit_key(req.headers(), peer, &ctx).await;
253 let limit = override_limit.unwrap_or(ctx.limiter.burst);
254
255 if limit == 0 {
256 return next.run(req).await;
257 }
258
259 let caller = check_rate_limit(&ctx.limiter, key, limit);
260 if !caller.allowed {
261 return too_many_requests(&caller);
262 }
263
264 let (req, result) = match ctx.account_limit {
265 AccountLimit::Off => (req, caller),
266 AccountLimit::ByEmail => {
267 let (parts, body) = req.into_parts();
268 let Ok(bytes) = to_bytes(body, ACCOUNT_BODY_LIMIT).await else {
269 return payload_too_large();
270 };
271 let account = account_key(&bytes);
272 let req = Request::from_parts(parts, Body::from(bytes));
273 match account {
274 None => (req, caller),
275 Some(key) => {
276 let account = check_rate_limit(&ctx.limiter, key, ctx.limiter.burst);
277 if !account.allowed {
278 return too_many_requests(&account);
279 }
280 let tightest = if account.remaining < caller.remaining {
281 account
282 } else {
283 caller
284 };
285 (req, tightest)
286 }
287 }
288 }
289 };
290
291 let mut resp = next.run(req).await;
292 insert_rate_limit_headers(resp.headers_mut(), &result);
293 resp
294}
295
296fn too_many_requests(result: &RateLimitResult) -> Response {
297 let retry_after = result.reset.saturating_sub(now_epoch()).max(1);
298
299 let body = json!({
300 "error": {
301 "code": "RATE_LIMIT_EXCEEDED",
302 "message": "Too many requests, please try again later",
303 "retry_after_secs": retry_after,
304 }
305 });
306
307 let mut resp = (StatusCode::TOO_MANY_REQUESTS, Json(body)).into_response();
308 resp.headers_mut()
309 .insert("retry-after", HeaderValue::from(retry_after));
310 insert_rate_limit_headers(resp.headers_mut(), result);
311 resp
312}
313
314fn payload_too_large() -> Response {
315 let body = json!({
316 "error": {
317 "code": "PAYLOAD_TOO_LARGE",
318 "message": "Request body too large or unreadable",
319 }
320 });
321 (StatusCode::PAYLOAD_TOO_LARGE, Json(body)).into_response()
322}
323
324fn insert_rate_limit_headers(headers: &mut HeaderMap, result: &RateLimitResult) {
325 headers.insert("x-ratelimit-limit", HeaderValue::from(result.limit));
326 headers.insert("x-ratelimit-remaining", HeaderValue::from(result.remaining));
327 headers.insert("x-ratelimit-reset", HeaderValue::from(result.reset));
328}
329
330async fn extract_rate_limit_key(
337 headers: &HeaderMap,
338 peer: Option<IpAddr>,
339 ctx: &RateLimitContext,
340) -> (RateLimitKey, Option<u32>) {
341 let bearer = headers
342 .get("authorization")
343 .and_then(|v| v.to_str().ok())
344 .and_then(|v| v.strip_prefix("Bearer "))
345 .map(|s| s.to_string());
346
347 if let Some(ref token) = bearer {
348 if token.starts_with(API_KEY_PREFIX) {
349 if let Some((key, override_limit)) = try_api_key_identity(token, ctx).await {
350 return (key, override_limit);
351 }
352 } else if let Some(key) = try_jwt_identity(token, ctx) {
353 return (key, None);
354 }
355 }
356
357 if let Some(cookie_header) = headers.get("cookie").and_then(|v| v.to_str().ok()) {
359 for part in cookie_header.split(';') {
360 let part = part.trim();
361 if let Some(value) = part.strip_prefix("ironflow_session=")
362 && let Some(key) = try_jwt_identity(value, ctx)
363 {
364 return (key, None);
365 }
366 }
367 }
368
369 let ip = client_ip(peer, headers, &ctx.trusted_proxies);
370 (RateLimitKey::Ip(ip), None)
371}
372
373async fn try_api_key_identity(
374 token: &str,
375 ctx: &RateLimitContext,
376) -> Option<(RateLimitKey, Option<u32>)> {
377 let suffix_len = (token.len() - API_KEY_PREFIX.len()).min(API_KEY_SUFFIX_LEN);
378 let prefix = &token[..API_KEY_PREFIX.len() + suffix_len];
379
380 let api_key = ctx.store.find_api_key_by_prefix(prefix).await.ok()??;
381
382 let override_limit = api_key.rate_limit_override;
383 Some((RateLimitKey::ApiKey(api_key.id), override_limit))
384}
385
386fn try_jwt_identity(token: &str, ctx: &RateLimitContext) -> Option<RateLimitKey> {
387 let claims = AccessToken::decode(token, &ctx.jwt_config).ok()?;
388 Some(RateLimitKey::User(claims.user_id))
389}