1use std::{num::NonZeroU32, time::Duration};
8
9use governor::Quota;
10use schemars::JsonSchema;
11use serde::{Deserialize, Serialize, de::Error as _};
12
13use crate::ConfigurationSection;
14
15#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
17pub struct RateLimitingConfig {
18    #[serde(default)]
20    pub account_recovery: AccountRecoveryRateLimitingConfig,
21
22    #[serde(default)]
24    pub login: LoginRateLimitingConfig,
25
26    #[serde(default = "default_registration")]
29    pub registration: RateLimiterConfiguration,
30
31    #[serde(default)]
33    pub email_authentication: EmailauthenticationRateLimitingConfig,
34}
35
36#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
37pub struct LoginRateLimitingConfig {
38    #[serde(default = "default_login_per_ip")]
45    pub per_ip: RateLimiterConfiguration,
46
47    #[serde(default = "default_login_per_account")]
56    pub per_account: RateLimiterConfiguration,
57}
58
59#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
60pub struct AccountRecoveryRateLimitingConfig {
61    #[serde(default = "default_account_recovery_per_ip")]
67    pub per_ip: RateLimiterConfiguration,
68
69    #[serde(default = "default_account_recovery_per_address")]
75    pub per_address: RateLimiterConfiguration,
76}
77
78#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
79pub struct EmailauthenticationRateLimitingConfig {
80    #[serde(default = "default_email_authentication_per_ip")]
84    pub per_ip: RateLimiterConfiguration,
85
86    #[serde(default = "default_email_authentication_per_address")]
92    pub per_address: RateLimiterConfiguration,
93
94    #[serde(default = "default_email_authentication_emails_per_session")]
98    pub emails_per_session: RateLimiterConfiguration,
99
100    #[serde(default = "default_email_authentication_attempt_per_session")]
104    pub attempt_per_session: RateLimiterConfiguration,
105}
106
107#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
108pub struct RateLimiterConfiguration {
109    pub burst: NonZeroU32,
112    pub per_second: f64,
115}
116
117impl ConfigurationSection for RateLimitingConfig {
118    const PATH: Option<&'static str> = Some("rate_limiting");
119
120    fn validate(&self, figment: &figment::Figment) -> Result<(), figment::Error> {
121        let metadata = figment.find_metadata(Self::PATH.unwrap());
122
123        let error_on_field = |mut error: figment::error::Error, field: &'static str| {
124            error.metadata = metadata.cloned();
125            error.profile = Some(figment::Profile::Default);
126            error.path = vec![Self::PATH.unwrap().to_owned(), field.to_owned()];
127            error
128        };
129
130        let error_on_nested_field =
131            |mut error: figment::error::Error, container: &'static str, field: &'static str| {
132                error.metadata = metadata.cloned();
133                error.profile = Some(figment::Profile::Default);
134                error.path = vec![
135                    Self::PATH.unwrap().to_owned(),
136                    container.to_owned(),
137                    field.to_owned(),
138                ];
139                error
140            };
141
142        let error_on_limiter =
144            |limiter: &RateLimiterConfiguration| -> Option<figment::error::Error> {
145                let recip = limiter.per_second.recip();
146                if recip < 1.0e-9 || !recip.is_finite() {
148                    return Some(figment::error::Error::custom(
149                        "`per_second` must be a number that is more than zero and less than 1_000_000_000 (1e9)",
150                    ));
151                }
152
153                None
154            };
155
156        if let Some(error) = error_on_limiter(&self.account_recovery.per_ip) {
157            return Err(error_on_nested_field(error, "account_recovery", "per_ip"));
158        }
159        if let Some(error) = error_on_limiter(&self.account_recovery.per_address) {
160            return Err(error_on_nested_field(
161                error,
162                "account_recovery",
163                "per_address",
164            ));
165        }
166
167        if let Some(error) = error_on_limiter(&self.registration) {
168            return Err(error_on_field(error, "registration"));
169        }
170
171        if let Some(error) = error_on_limiter(&self.login.per_ip) {
172            return Err(error_on_nested_field(error, "login", "per_ip"));
173        }
174        if let Some(error) = error_on_limiter(&self.login.per_account) {
175            return Err(error_on_nested_field(error, "login", "per_account"));
176        }
177
178        Ok(())
179    }
180}
181
182impl RateLimitingConfig {
183    pub(crate) fn is_default(config: &RateLimitingConfig) -> bool {
184        config == &RateLimitingConfig::default()
185    }
186}
187
188impl RateLimiterConfiguration {
189    pub fn to_quota(self) -> Option<Quota> {
190        let reciprocal = self.per_second.recip();
191        if !reciprocal.is_finite() {
192            return None;
193        }
194        Some(Quota::with_period(Duration::from_secs_f64(reciprocal))?.allow_burst(self.burst))
195    }
196}
197
198fn default_login_per_ip() -> RateLimiterConfiguration {
199    RateLimiterConfiguration {
200        burst: NonZeroU32::new(3).unwrap(),
201        per_second: 3.0 / 60.0,
202    }
203}
204
205fn default_login_per_account() -> RateLimiterConfiguration {
206    RateLimiterConfiguration {
207        burst: NonZeroU32::new(1800).unwrap(),
208        per_second: 1800.0 / 3600.0,
209    }
210}
211
212fn default_registration() -> RateLimiterConfiguration {
213    RateLimiterConfiguration {
214        burst: NonZeroU32::new(3).unwrap(),
215        per_second: 3.0 / 3600.0,
216    }
217}
218
219fn default_account_recovery_per_ip() -> RateLimiterConfiguration {
220    RateLimiterConfiguration {
221        burst: NonZeroU32::new(3).unwrap(),
222        per_second: 3.0 / 3600.0,
223    }
224}
225
226fn default_account_recovery_per_address() -> RateLimiterConfiguration {
227    RateLimiterConfiguration {
228        burst: NonZeroU32::new(3).unwrap(),
229        per_second: 1.0 / 3600.0,
230    }
231}
232
233fn default_email_authentication_per_ip() -> RateLimiterConfiguration {
234    RateLimiterConfiguration {
235        burst: NonZeroU32::new(5).unwrap(),
236        per_second: 1.0 / 60.0,
237    }
238}
239
240fn default_email_authentication_per_address() -> RateLimiterConfiguration {
241    RateLimiterConfiguration {
242        burst: NonZeroU32::new(3).unwrap(),
243        per_second: 1.0 / 3600.0,
244    }
245}
246
247fn default_email_authentication_emails_per_session() -> RateLimiterConfiguration {
248    RateLimiterConfiguration {
249        burst: NonZeroU32::new(2).unwrap(),
250        per_second: 1.0 / 300.0,
251    }
252}
253
254fn default_email_authentication_attempt_per_session() -> RateLimiterConfiguration {
255    RateLimiterConfiguration {
256        burst: NonZeroU32::new(10).unwrap(),
257        per_second: 1.0 / 60.0,
258    }
259}
260
261impl Default for RateLimitingConfig {
262    fn default() -> Self {
263        RateLimitingConfig {
264            login: LoginRateLimitingConfig::default(),
265            registration: default_registration(),
266            account_recovery: AccountRecoveryRateLimitingConfig::default(),
267            email_authentication: EmailauthenticationRateLimitingConfig::default(),
268        }
269    }
270}
271
272impl Default for LoginRateLimitingConfig {
273    fn default() -> Self {
274        LoginRateLimitingConfig {
275            per_ip: default_login_per_ip(),
276            per_account: default_login_per_account(),
277        }
278    }
279}
280
281impl Default for AccountRecoveryRateLimitingConfig {
282    fn default() -> Self {
283        AccountRecoveryRateLimitingConfig {
284            per_ip: default_account_recovery_per_ip(),
285            per_address: default_account_recovery_per_address(),
286        }
287    }
288}
289
290impl Default for EmailauthenticationRateLimitingConfig {
291    fn default() -> Self {
292        EmailauthenticationRateLimitingConfig {
293            per_ip: default_email_authentication_per_ip(),
294            per_address: default_email_authentication_per_address(),
295            emails_per_session: default_email_authentication_emails_per_session(),
296            attempt_per_session: default_email_authentication_attempt_per_session(),
297        }
298    }
299}