1use std::{num::NonZeroU32, time::Duration};
9
10use governor::Quota;
11use schemars::JsonSchema;
12use serde::{Deserialize, Serialize, de::Error as _};
13
14use crate::ConfigurationSection;
15
16#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
18pub struct RateLimitingConfig {
19 #[serde(default)]
21 pub account_recovery: AccountRecoveryRateLimitingConfig,
22
23 #[serde(default)]
25 pub login: LoginRateLimitingConfig,
26
27 #[serde(default = "default_registration")]
30 pub registration: RateLimiterConfiguration,
31
32 #[serde(default)]
34 pub email_authentication: EmailauthenticationRateLimitingConfig,
35
36 #[serde(default = "default_device_code_link")]
41 pub device_code_link: RateLimiterConfiguration,
42
43 #[serde(default = "default_username_availability")]
49 pub username_availability: RateLimiterConfiguration,
50}
51
52#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
53pub struct LoginRateLimitingConfig {
54 #[serde(default = "default_login_per_ip")]
61 pub per_ip: RateLimiterConfiguration,
62
63 #[serde(default = "default_login_per_account")]
72 pub per_account: RateLimiterConfiguration,
73}
74
75#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
76pub struct AccountRecoveryRateLimitingConfig {
77 #[serde(default = "default_account_recovery_per_ip")]
83 pub per_ip: RateLimiterConfiguration,
84
85 #[serde(default = "default_account_recovery_per_address")]
91 pub per_address: RateLimiterConfiguration,
92}
93
94#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
95pub struct EmailauthenticationRateLimitingConfig {
96 #[serde(default = "default_email_authentication_per_ip")]
100 pub per_ip: RateLimiterConfiguration,
101
102 #[serde(default = "default_email_authentication_per_address")]
108 pub per_address: RateLimiterConfiguration,
109
110 #[serde(default = "default_email_authentication_emails_per_session")]
114 pub emails_per_session: RateLimiterConfiguration,
115
116 #[serde(default = "default_email_authentication_attempt_per_session")]
120 pub attempt_per_session: RateLimiterConfiguration,
121}
122
123#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
124pub struct RateLimiterConfiguration {
125 pub burst: NonZeroU32,
128 pub per_second: f64,
131}
132
133impl ConfigurationSection for RateLimitingConfig {
134 const PATH: Option<&'static str> = Some("rate_limiting");
135
136 fn validate(
137 &self,
138 figment: &figment::Figment,
139 ) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
140 let metadata = figment.find_metadata(Self::PATH.unwrap());
141
142 let error_on_field = |mut error: figment::error::Error, field: &'static str| {
143 error.metadata = metadata.cloned();
144 error.profile = Some(figment::Profile::Default);
145 error.path = vec![Self::PATH.unwrap().to_owned(), field.to_owned()];
146 error
147 };
148
149 let error_on_nested_field =
150 |mut error: figment::error::Error, container: &'static str, field: &'static str| {
151 error.metadata = metadata.cloned();
152 error.profile = Some(figment::Profile::Default);
153 error.path = vec![
154 Self::PATH.unwrap().to_owned(),
155 container.to_owned(),
156 field.to_owned(),
157 ];
158 error
159 };
160
161 let error_on_limiter =
163 |limiter: &RateLimiterConfiguration| -> Option<figment::error::Error> {
164 let recip = limiter.per_second.recip();
165 if recip < 1.0e-9 || !recip.is_finite() {
167 return Some(figment::error::Error::custom(
168 "`per_second` must be a number that is more than zero and less than 1_000_000_000 (1e9)",
169 ));
170 }
171
172 None
173 };
174
175 if let Some(error) = error_on_limiter(&self.account_recovery.per_ip) {
176 return Err(error_on_nested_field(error, "account_recovery", "per_ip").into());
177 }
178 if let Some(error) = error_on_limiter(&self.account_recovery.per_address) {
179 return Err(error_on_nested_field(error, "account_recovery", "per_address").into());
180 }
181
182 if let Some(error) = error_on_limiter(&self.registration) {
183 return Err(error_on_field(error, "registration").into());
184 }
185
186 if let Some(error) = error_on_limiter(&self.username_availability) {
187 return Err(error_on_field(error, "username_availability").into());
188 }
189
190 if let Some(error) = error_on_limiter(&self.login.per_ip) {
191 return Err(error_on_nested_field(error, "login", "per_ip").into());
192 }
193 if let Some(error) = error_on_limiter(&self.login.per_account) {
194 return Err(error_on_nested_field(error, "login", "per_account").into());
195 }
196
197 if let Some(error) = error_on_limiter(&self.device_code_link) {
198 return Err(error_on_field(error, "device_code_link").into());
199 }
200
201 Ok(())
202 }
203}
204
205impl RateLimitingConfig {
206 pub(crate) fn is_default(config: &RateLimitingConfig) -> bool {
207 config == &RateLimitingConfig::default()
208 }
209}
210
211impl RateLimiterConfiguration {
212 pub fn to_quota(self) -> Option<Quota> {
213 let reciprocal = self.per_second.recip();
214 if !reciprocal.is_finite() {
215 return None;
216 }
217 Some(Quota::with_period(Duration::from_secs_f64(reciprocal))?.allow_burst(self.burst))
218 }
219}
220
221fn default_login_per_ip() -> RateLimiterConfiguration {
222 RateLimiterConfiguration {
223 burst: NonZeroU32::new(3).unwrap(),
224 per_second: 3.0 / 60.0,
225 }
226}
227
228fn default_login_per_account() -> RateLimiterConfiguration {
229 RateLimiterConfiguration {
230 burst: NonZeroU32::new(1800).unwrap(),
231 per_second: 1800.0 / 3600.0,
232 }
233}
234
235fn default_registration() -> RateLimiterConfiguration {
236 RateLimiterConfiguration {
237 burst: NonZeroU32::new(3).unwrap(),
238 per_second: 3.0 / 3600.0,
239 }
240}
241
242fn default_account_recovery_per_ip() -> RateLimiterConfiguration {
243 RateLimiterConfiguration {
244 burst: NonZeroU32::new(3).unwrap(),
245 per_second: 3.0 / 3600.0,
246 }
247}
248
249fn default_account_recovery_per_address() -> RateLimiterConfiguration {
250 RateLimiterConfiguration {
251 burst: NonZeroU32::new(3).unwrap(),
252 per_second: 1.0 / 3600.0,
253 }
254}
255
256fn default_email_authentication_per_ip() -> RateLimiterConfiguration {
257 RateLimiterConfiguration {
258 burst: NonZeroU32::new(5).unwrap(),
259 per_second: 1.0 / 60.0,
260 }
261}
262
263fn default_email_authentication_per_address() -> RateLimiterConfiguration {
264 RateLimiterConfiguration {
265 burst: NonZeroU32::new(3).unwrap(),
266 per_second: 1.0 / 3600.0,
267 }
268}
269
270fn default_email_authentication_emails_per_session() -> RateLimiterConfiguration {
271 RateLimiterConfiguration {
272 burst: NonZeroU32::new(2).unwrap(),
273 per_second: 1.0 / 300.0,
274 }
275}
276
277fn default_email_authentication_attempt_per_session() -> RateLimiterConfiguration {
278 RateLimiterConfiguration {
279 burst: NonZeroU32::new(10).unwrap(),
280 per_second: 1.0 / 60.0,
281 }
282}
283
284fn default_device_code_link() -> RateLimiterConfiguration {
285 RateLimiterConfiguration {
286 burst: NonZeroU32::new(10).unwrap(),
287 per_second: 1.0 / 60.0,
288 }
289}
290
291fn default_username_availability() -> RateLimiterConfiguration {
292 RateLimiterConfiguration {
293 burst: NonZeroU32::new(5).unwrap(),
294 per_second: 1.0,
295 }
296}
297
298impl Default for RateLimitingConfig {
299 fn default() -> Self {
300 RateLimitingConfig {
301 login: LoginRateLimitingConfig::default(),
302 registration: default_registration(),
303 account_recovery: AccountRecoveryRateLimitingConfig::default(),
304 email_authentication: EmailauthenticationRateLimitingConfig::default(),
305 device_code_link: default_device_code_link(),
306 username_availability: default_username_availability(),
307 }
308 }
309}
310
311impl Default for LoginRateLimitingConfig {
312 fn default() -> Self {
313 LoginRateLimitingConfig {
314 per_ip: default_login_per_ip(),
315 per_account: default_login_per_account(),
316 }
317 }
318}
319
320impl Default for AccountRecoveryRateLimitingConfig {
321 fn default() -> Self {
322 AccountRecoveryRateLimitingConfig {
323 per_ip: default_account_recovery_per_ip(),
324 per_address: default_account_recovery_per_address(),
325 }
326 }
327}
328
329impl Default for EmailauthenticationRateLimitingConfig {
330 fn default() -> Self {
331 EmailauthenticationRateLimitingConfig {
332 per_ip: default_email_authentication_per_ip(),
333 per_address: default_email_authentication_per_address(),
334 emails_per_session: default_email_authentication_emails_per_session(),
335 attempt_per_session: default_email_authentication_attempt_per_session(),
336 }
337 }
338}