Skip to main content

mentedb_server/
rate_limit.rs

1//! Token bucket rate limiter implemented as a tower Layer/Service.
2//!
3//! Requests are counted per caller: the token's `sub` (else `jti`, else its
4//! agent) when the request is authenticated, otherwise the peer IP address.
5//! The layer must sit inside the auth middleware so it sees the caller.
6
7use std::collections::HashMap;
8use std::net::IpAddr;
9use std::sync::Arc;
10use std::task::{Context, Poll};
11use std::time::Instant;
12
13use axum::body::Body;
14use axum::http::{Request, StatusCode};
15use axum::response::IntoResponse;
16use serde_json::json;
17use tokio::sync::Mutex;
18use tower::{Layer, Service};
19
20/// Default bucket size (`MENTEDB_RATE_LIMIT_BURST`).
21pub const DEFAULT_BURST: u32 = 100;
22/// Default refill per second (`MENTEDB_RATE_LIMIT_PER_SEC`).
23pub const DEFAULT_PER_SEC: u32 = 10;
24/// Buckets kept before idle (full) ones are dropped.
25const MAX_TRACKED_KEYS: usize = 100_000;
26
27/// What requests are counted under.
28#[derive(Clone, Debug, PartialEq, Eq, Hash)]
29enum Key {
30    Caller(String),
31    Ip(IpAddr),
32}
33
34/// One caller's token bucket.
35struct Bucket {
36    tokens: f64,
37    last_refill: Instant,
38}
39
40/// Shared rate limiter state.
41#[derive(Clone)]
42pub struct RateLimiter {
43    buckets: Arc<Mutex<HashMap<Key, Bucket>>>,
44    max_tokens: u32,
45    refill_rate: u32,
46}
47
48impl RateLimiter {
49    /// A limiter allowing bursts of `max_tokens` refilled at `refill_rate` per
50    /// second. Either at 0 turns limiting off.
51    pub fn new(max_tokens: u32, refill_rate: u32) -> Self {
52        Self {
53            buckets: Arc::new(Mutex::new(HashMap::new())),
54            max_tokens,
55            refill_rate,
56        }
57    }
58
59    /// `MENTEDB_RATE_LIMIT_BURST` and `MENTEDB_RATE_LIMIT_PER_SEC` (0 for
60    /// either turns limiting off), defaulting to 100 and 10.
61    pub fn from_env() -> Self {
62        let read = |name: &str, default: u32| {
63            std::env::var(name)
64                .ok()
65                .and_then(|v| v.trim().parse::<u32>().ok())
66                .unwrap_or(default)
67        };
68        Self::new(
69            read("MENTEDB_RATE_LIMIT_BURST", DEFAULT_BURST),
70            read("MENTEDB_RATE_LIMIT_PER_SEC", DEFAULT_PER_SEC),
71        )
72    }
73
74    fn enabled(&self) -> bool {
75        self.max_tokens > 0 && self.refill_rate > 0
76    }
77
78    async fn allow(&self, key: Key) -> bool {
79        let now = Instant::now();
80        let max = f64::from(self.max_tokens);
81        let rate = f64::from(self.refill_rate);
82        let mut buckets = self.buckets.lock().await;
83        if buckets.len() >= MAX_TRACKED_KEYS {
84            // A full bucket carries no state worth keeping.
85            buckets.retain(|_, b| {
86                b.tokens + now.duration_since(b.last_refill).as_secs_f64() * rate < max
87            });
88        }
89        let bucket = buckets.entry(key).or_insert(Bucket {
90            tokens: max,
91            last_refill: now,
92        });
93        let elapsed = now.duration_since(bucket.last_refill).as_secs_f64();
94        bucket.tokens = (bucket.tokens + elapsed * rate).min(max);
95        bucket.last_refill = now;
96        if bucket.tokens >= 1.0 {
97            bucket.tokens -= 1.0;
98            true
99        } else {
100            false
101        }
102    }
103}
104
105impl Default for RateLimiter {
106    fn default() -> Self {
107        Self::new(DEFAULT_BURST, DEFAULT_PER_SEC)
108    }
109}
110
111// -- Tower Layer --
112
113impl<S> Layer<S> for RateLimiter {
114    type Service = RateLimitService<S>;
115
116    fn layer(&self, inner: S) -> Self::Service {
117        RateLimitService {
118            inner,
119            limiter: self.clone(),
120        }
121    }
122}
123
124// -- Tower Service --
125
126#[derive(Clone)]
127pub struct RateLimitService<S> {
128    inner: S,
129    limiter: RateLimiter,
130}
131
132impl<S> Service<Request<Body>> for RateLimitService<S>
133where
134    S: Service<Request<Body>, Response = axum::response::Response> + Clone + Send + 'static,
135    S::Future: Send + 'static,
136{
137    type Response = axum::response::Response;
138    type Error = S::Error;
139    type Future = std::pin::Pin<
140        Box<dyn std::future::Future<Output = Result<Self::Response, Self::Error>> + Send>,
141    >;
142
143    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
144        self.inner.poll_ready(cx)
145    }
146
147    fn call(&mut self, req: Request<Body>) -> Self::Future {
148        let limiter = self.limiter.clone();
149        let mut inner = self.inner.clone();
150
151        let key = req
152            .extensions()
153            .get::<crate::auth::AuthenticatedAgent>()
154            .and_then(|a| a.rate_key())
155            .map(Key::Caller)
156            .unwrap_or_else(|| {
157                Key::Ip(
158                    req.extensions()
159                        .get::<axum::extract::ConnectInfo<std::net::SocketAddr>>()
160                        .map(|ci| ci.0.ip())
161                        .unwrap_or(IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED)),
162                )
163            });
164
165        Box::pin(async move {
166            if !limiter.enabled() || limiter.allow(key).await {
167                inner.call(req).await
168            } else {
169                let body = json!({ "error": "too many requests" });
170                Ok((StatusCode::TOO_MANY_REQUESTS, axum::Json(body)).into_response())
171            }
172        })
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179
180    #[tokio::test]
181    async fn buckets_are_per_key_and_refill() {
182        let l = RateLimiter::new(2, 1000);
183        let a = Key::Caller("alice".into());
184        let b = Key::Caller("bob".into());
185        assert!(l.allow(a.clone()).await);
186        assert!(l.allow(a.clone()).await);
187        assert!(!l.allow(a.clone()).await, "alice's burst is spent");
188        assert!(l.allow(b.clone()).await, "bob has his own bucket");
189        tokio::time::sleep(std::time::Duration::from_millis(5)).await;
190        assert!(l.allow(a).await, "refilled");
191    }
192
193    #[test]
194    fn zero_turns_limiting_off() {
195        assert!(!RateLimiter::new(0, 10).enabled());
196        assert!(!RateLimiter::new(10, 0).enabled());
197        assert!(RateLimiter::new(1, 1).enabled());
198    }
199}