arc_web/helpers/
rate_limit.rs1use std::collections::HashMap;
2use std::env;
3use std::sync::{Arc, Mutex};
4use std::time::{Duration, Instant};
5use tracing::info;
6
7#[derive(Clone)]
8pub struct RateLimiter {
9 max_requests: usize,
10 period: Duration,
11 requests: Arc<Mutex<HashMap<String, Vec<Instant>>>>,
12}
13
14#[derive(Clone)]
16pub struct LoginRateLimiter(pub RateLimiter);
17
18impl std::ops::Deref for LoginRateLimiter {
19 type Target = RateLimiter;
20 fn deref(&self) -> &Self::Target {
21 &self.0
22 }
23}
24
25impl RateLimiter {
26 pub fn new(max_requests: usize, period: Duration) -> Self {
27 Self {
28 max_requests,
29 period,
30 requests: Arc::new(Mutex::new(HashMap::new())),
31 }
32 }
33
34 pub fn check(&self, key: String) -> Result<(), Duration> {
37 let now = Instant::now();
38 let mut requests = self.requests.lock().unwrap();
39
40 let key_requests = requests.entry(key).or_default();
42
43 key_requests.retain(|&time| now.duration_since(time) < self.period);
45
46 if key_requests.len() >= self.max_requests {
48 if let Some(&oldest) = key_requests.first() {
50 let retry_after = self.period.saturating_sub(now.duration_since(oldest));
51 return Err(retry_after);
52 }
53 return Err(self.period);
54 }
55
56 key_requests.push(now);
58 Ok(())
59 }
60}
61
62pub fn create_rate_limiter() -> LoginRateLimiter {
64 let max_requests = env::var("RATE_LIMIT_MAX_REQUESTS")
65 .ok()
66 .and_then(|s| s.parse::<usize>().ok())
67 .unwrap_or(5);
68
69 let period_secs = env::var("RATE_LIMIT_PERIOD_SECS")
70 .ok()
71 .and_then(|s| s.parse::<u64>().ok())
72 .unwrap_or(60);
73
74 info!(
75 max_requests = max_requests,
76 period_secs = period_secs,
77 "Configuring login rate limiter"
78 );
79
80 LoginRateLimiter(RateLimiter::new(
81 max_requests,
82 Duration::from_secs(period_secs),
83 ))
84}
85
86pub fn create_global_rate_limiter() -> RateLimiter {
88 let max_requests = env::var("GLOBAL_RATE_LIMIT_MAX_REQUESTS")
89 .ok()
90 .and_then(|s| s.parse::<usize>().ok())
91 .unwrap_or(100);
92
93 let period_secs = env::var("GLOBAL_RATE_LIMIT_PERIOD_SECS")
94 .ok()
95 .and_then(|s| s.parse::<u64>().ok())
96 .unwrap_or(60);
97
98 info!(
99 max_requests = max_requests,
100 period_secs = period_secs,
101 "Configuring global rate limiter"
102 );
103
104 RateLimiter::new(max_requests, Duration::from_secs(period_secs))
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110 use std::thread::sleep;
111
112 #[test]
113 fn test_rate_limiter_allows_within_limit() {
114 let limiter = RateLimiter::new(3, Duration::from_secs(1));
115
116 assert!(limiter.check("test_key".to_string()).is_ok());
117 assert!(limiter.check("test_key".to_string()).is_ok());
118 assert!(limiter.check("test_key".to_string()).is_ok());
119 }
120
121 #[test]
122 fn test_rate_limiter_blocks_over_limit() {
123 let limiter = RateLimiter::new(2, Duration::from_secs(1));
124
125 assert!(limiter.check("test_key".to_string()).is_ok());
126 assert!(limiter.check("test_key".to_string()).is_ok());
127 assert!(limiter.check("test_key".to_string()).is_err());
128 }
129
130 #[test]
131 fn test_rate_limiter_resets_after_period() {
132 let limiter = RateLimiter::new(2, Duration::from_millis(100));
133
134 assert!(limiter.check("test_key".to_string()).is_ok());
135 assert!(limiter.check("test_key".to_string()).is_ok());
136 assert!(limiter.check("test_key".to_string()).is_err());
137
138 sleep(Duration::from_millis(150));
139
140 assert!(limiter.check("test_key".to_string()).is_ok());
142 }
143
144 #[test]
145 fn test_rate_limiter_separate_keys() {
146 let limiter = RateLimiter::new(1, Duration::from_secs(1));
147
148 assert!(limiter.check("key1".to_string()).is_ok());
149 assert!(limiter.check("key2".to_string()).is_ok());
150
151 assert!(limiter.check("key1".to_string()).is_err());
153 assert!(limiter.check("key2".to_string()).is_err());
154 }
155}