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}