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 #[serde(default = "default_device_code_link")]
40 pub device_code_link: RateLimiterConfiguration,
41}
42
43#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
44pub struct LoginRateLimitingConfig {
45 #[serde(default = "default_login_per_ip")]
52 pub per_ip: RateLimiterConfiguration,
53
54 #[serde(default = "default_login_per_account")]
63 pub per_account: RateLimiterConfiguration,
64}
65
66#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
67pub struct AccountRecoveryRateLimitingConfig {
68 #[serde(default = "default_account_recovery_per_ip")]
74 pub per_ip: RateLimiterConfiguration,
75
76 #[serde(default = "default_account_recovery_per_address")]
82 pub per_address: RateLimiterConfiguration,
83}
84
85#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
86pub struct EmailauthenticationRateLimitingConfig {
87 #[serde(default = "default_email_authentication_per_ip")]
91 pub per_ip: RateLimiterConfiguration,
92
93 #[serde(default = "default_email_authentication_per_address")]
99 pub per_address: RateLimiterConfiguration,
100
101 #[serde(default = "default_email_authentication_emails_per_session")]
105 pub emails_per_session: RateLimiterConfiguration,
106
107 #[serde(default = "default_email_authentication_attempt_per_session")]
111 pub attempt_per_session: RateLimiterConfiguration,
112}
113
114#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
115pub struct RateLimiterConfiguration {
116 pub burst: NonZeroU32,
119 pub per_second: f64,
122}
123
124impl ConfigurationSection for RateLimitingConfig {
125 const PATH: Option<&'static str> = Some("rate_limiting");
126
127 fn validate(
128 &self,
129 figment: &figment::Figment,
130 ) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
131 let metadata = figment.find_metadata(Self::PATH.unwrap());
132
133 let error_on_field = |mut error: figment::error::Error, field: &'static str| {
134 error.metadata = metadata.cloned();
135 error.profile = Some(figment::Profile::Default);
136 error.path = vec![Self::PATH.unwrap().to_owned(), field.to_owned()];
137 error
138 };
139
140 let error_on_nested_field =
141 |mut error: figment::error::Error, container: &'static str, field: &'static str| {
142 error.metadata = metadata.cloned();
143 error.profile = Some(figment::Profile::Default);
144 error.path = vec![
145 Self::PATH.unwrap().to_owned(),
146 container.to_owned(),
147 field.to_owned(),
148 ];
149 error
150 };
151
152 let error_on_limiter =
154 |limiter: &RateLimiterConfiguration| -> Option<figment::error::Error> {
155 let recip = limiter.per_second.recip();
156 if recip < 1.0e-9 || !recip.is_finite() {
158 return Some(figment::error::Error::custom(
159 "`per_second` must be a number that is more than zero and less than 1_000_000_000 (1e9)",
160 ));
161 }
162
163 None
164 };
165
166 if let Some(error) = error_on_limiter(&self.account_recovery.per_ip) {
167 return Err(error_on_nested_field(error, "account_recovery", "per_ip").into());
168 }
169 if let Some(error) = error_on_limiter(&self.account_recovery.per_address) {
170 return Err(error_on_nested_field(error, "account_recovery", "per_address").into());
171 }
172
173 if let Some(error) = error_on_limiter(&self.registration) {
174 return Err(error_on_field(error, "registration").into());
175 }
176
177 if let Some(error) = error_on_limiter(&self.login.per_ip) {
178 return Err(error_on_nested_field(error, "login", "per_ip").into());
179 }
180 if let Some(error) = error_on_limiter(&self.login.per_account) {
181 return Err(error_on_nested_field(error, "login", "per_account").into());
182 }
183
184 if let Some(error) = error_on_limiter(&self.device_code_link) {
185 return Err(error_on_field(error, "device_code_link").into());
186 }
187
188 Ok(())
189 }
190}
191
192impl RateLimitingConfig {
193 pub(crate) fn is_default(config: &RateLimitingConfig) -> bool {
194 config == &RateLimitingConfig::default()
195 }
196}
197
198impl RateLimiterConfiguration {
199 pub fn to_quota(self) -> Option<Quota> {
200 let reciprocal = self.per_second.recip();
201 if !reciprocal.is_finite() {
202 return None;
203 }
204 Some(Quota::with_period(Duration::from_secs_f64(reciprocal))?.allow_burst(self.burst))
205 }
206}
207
208fn default_login_per_ip() -> RateLimiterConfiguration {
209 RateLimiterConfiguration {
210 burst: NonZeroU32::new(3).unwrap(),
211 per_second: 3.0 / 60.0,
212 }
213}
214
215fn default_login_per_account() -> RateLimiterConfiguration {
216 RateLimiterConfiguration {
217 burst: NonZeroU32::new(1800).unwrap(),
218 per_second: 1800.0 / 3600.0,
219 }
220}
221
222fn default_registration() -> RateLimiterConfiguration {
223 RateLimiterConfiguration {
224 burst: NonZeroU32::new(3).unwrap(),
225 per_second: 3.0 / 3600.0,
226 }
227}
228
229fn default_account_recovery_per_ip() -> RateLimiterConfiguration {
230 RateLimiterConfiguration {
231 burst: NonZeroU32::new(3).unwrap(),
232 per_second: 3.0 / 3600.0,
233 }
234}
235
236fn default_account_recovery_per_address() -> RateLimiterConfiguration {
237 RateLimiterConfiguration {
238 burst: NonZeroU32::new(3).unwrap(),
239 per_second: 1.0 / 3600.0,
240 }
241}
242
243fn default_email_authentication_per_ip() -> RateLimiterConfiguration {
244 RateLimiterConfiguration {
245 burst: NonZeroU32::new(5).unwrap(),
246 per_second: 1.0 / 60.0,
247 }
248}
249
250fn default_email_authentication_per_address() -> RateLimiterConfiguration {
251 RateLimiterConfiguration {
252 burst: NonZeroU32::new(3).unwrap(),
253 per_second: 1.0 / 3600.0,
254 }
255}
256
257fn default_email_authentication_emails_per_session() -> RateLimiterConfiguration {
258 RateLimiterConfiguration {
259 burst: NonZeroU32::new(2).unwrap(),
260 per_second: 1.0 / 300.0,
261 }
262}
263
264fn default_email_authentication_attempt_per_session() -> RateLimiterConfiguration {
265 RateLimiterConfiguration {
266 burst: NonZeroU32::new(10).unwrap(),
267 per_second: 1.0 / 60.0,
268 }
269}
270
271fn default_device_code_link() -> RateLimiterConfiguration {
272 RateLimiterConfiguration {
273 burst: NonZeroU32::new(10).unwrap(),
274 per_second: 1.0 / 60.0,
275 }
276}
277
278impl Default for RateLimitingConfig {
279 fn default() -> Self {
280 RateLimitingConfig {
281 login: LoginRateLimitingConfig::default(),
282 registration: default_registration(),
283 account_recovery: AccountRecoveryRateLimitingConfig::default(),
284 email_authentication: EmailauthenticationRateLimitingConfig::default(),
285 device_code_link: default_device_code_link(),
286 }
287 }
288}
289
290impl Default for LoginRateLimitingConfig {
291 fn default() -> Self {
292 LoginRateLimitingConfig {
293 per_ip: default_login_per_ip(),
294 per_account: default_login_per_account(),
295 }
296 }
297}
298
299impl Default for AccountRecoveryRateLimitingConfig {
300 fn default() -> Self {
301 AccountRecoveryRateLimitingConfig {
302 per_ip: default_account_recovery_per_ip(),
303 per_address: default_account_recovery_per_address(),
304 }
305 }
306}
307
308impl Default for EmailauthenticationRateLimitingConfig {
309 fn default() -> Self {
310 EmailauthenticationRateLimitingConfig {
311 per_ip: default_email_authentication_per_ip(),
312 per_address: default_email_authentication_per_address(),
313 emails_per_session: default_email_authentication_emails_per_session(),
314 attempt_per_session: default_email_authentication_attempt_per_session(),
315 }
316 }
317}