@@ -23,21 +23,28 @@ use crate::ConfigurationSection;
23
23
/// Configuration related to sending emails
24
24
#[ derive( Clone , Debug , Serialize , Deserialize , JsonSchema , PartialEq ) ]
25
25
pub struct RateLimitingConfig {
26
+ /// Account Recovery-specific rate limits
27
+ #[ serde( default ) ]
28
+ pub account_recovery : AccountRecoveryRateLimitingConfig ,
26
29
/// Login-specific rate limits
27
30
#[ serde( default ) ]
28
31
pub login : LoginRateLimitingConfig ,
32
+ /// Controls how many registrations attempts are permitted
33
+ /// based on source address.
34
+ #[ serde( default = "default_registration" ) ]
35
+ pub registration : RateLimiterConfiguration ,
29
36
}
30
37
31
38
#[ derive( Clone , Debug , Serialize , Deserialize , JsonSchema , PartialEq ) ]
32
39
pub struct LoginRateLimitingConfig {
33
40
/// Controls how many login attempts are permitted
34
- /// based on source address.
41
+ /// based on source IP address.
35
42
/// This can protect against brute force login attempts.
36
43
///
37
44
/// Note: this limit also applies to password checks when a user attempts to
38
45
/// change their own password.
39
- #[ serde( default = "default_login_per_address " ) ]
40
- pub per_address : RateLimiterConfiguration ,
46
+ #[ serde( default = "default_login_per_ip " ) ]
47
+ pub per_ip : RateLimiterConfiguration ,
41
48
/// Controls how many login attempts are permitted
42
49
/// based on the account that is being attempted to be logged into.
43
50
/// This can protect against a distributed brute force attack
@@ -50,6 +57,24 @@ pub struct LoginRateLimitingConfig {
50
57
pub per_account : RateLimiterConfiguration ,
51
58
}
52
59
60
+ #[ derive( Clone , Debug , Serialize , Deserialize , JsonSchema , PartialEq ) ]
61
+ pub struct AccountRecoveryRateLimitingConfig {
62
+ /// Controls how many account recovery attempts are permitted
63
+ /// based on source IP address.
64
+ /// This can protect against causing e-mail spam to many targets.
65
+ ///
66
+ /// Note: this limit also applies to re-sends.
67
+ #[ serde( default = "default_account_recovery_per_ip" ) ]
68
+ pub per_ip : RateLimiterConfiguration ,
69
+ /// Controls how many account recovery attempts are permitted
70
+ /// based on the e-mail address entered into the recovery form.
71
+ /// This can protect against causing e-mail spam to one target.
72
+ ///
73
+ /// Note: this limit also applies to re-sends.
74
+ #[ serde( default = "default_account_recovery_per_address" ) ]
75
+ pub per_address : RateLimiterConfiguration ,
76
+ }
77
+
53
78
#[ derive( Copy , Clone , Debug , Serialize , Deserialize , JsonSchema , PartialEq ) ]
54
79
pub struct RateLimiterConfiguration {
55
80
/// A one-off burst of actions that the user can perform
@@ -66,6 +91,13 @@ impl ConfigurationSection for RateLimitingConfig {
66
91
fn validate ( & self , figment : & figment:: Figment ) -> Result < ( ) , figment:: Error > {
67
92
let metadata = figment. find_metadata ( Self :: PATH . unwrap ( ) ) ;
68
93
94
+ let error_on_field = |mut error : figment:: error:: Error , field : & ' static str | {
95
+ error. metadata = metadata. cloned ( ) ;
96
+ error. profile = Some ( figment:: Profile :: Default ) ;
97
+ error. path = vec ! [ Self :: PATH . unwrap( ) . to_owned( ) , field. to_owned( ) ] ;
98
+ error
99
+ } ;
100
+
69
101
let error_on_nested_field =
70
102
|mut error : figment:: error:: Error , container : & ' static str , field : & ' static str | {
71
103
error. metadata = metadata. cloned ( ) ;
@@ -92,8 +124,23 @@ impl ConfigurationSection for RateLimitingConfig {
92
124
None
93
125
} ;
94
126
95
- if let Some ( error) = error_on_limiter ( & self . login . per_address ) {
96
- return Err ( error_on_nested_field ( error, "login" , "per_address" ) ) ;
127
+ if let Some ( error) = error_on_limiter ( & self . account_recovery . per_ip ) {
128
+ return Err ( error_on_nested_field ( error, "account_recovery" , "per_ip" ) ) ;
129
+ }
130
+ if let Some ( error) = error_on_limiter ( & self . account_recovery . per_address ) {
131
+ return Err ( error_on_nested_field (
132
+ error,
133
+ "account_recovery" ,
134
+ "per_address" ,
135
+ ) ) ;
136
+ }
137
+
138
+ if let Some ( error) = error_on_limiter ( & self . registration ) {
139
+ return Err ( error_on_field ( error, "registration" ) ) ;
140
+ }
141
+
142
+ if let Some ( error) = error_on_limiter ( & self . login . per_ip ) {
143
+ return Err ( error_on_nested_field ( error, "login" , "per_ip" ) ) ;
97
144
}
98
145
if let Some ( error) = error_on_limiter ( & self . login . per_account ) {
99
146
return Err ( error_on_nested_field ( error, "login" , "per_account" ) ) ;
@@ -119,7 +166,7 @@ impl RateLimiterConfiguration {
119
166
}
120
167
}
121
168
122
- fn default_login_per_address ( ) -> RateLimiterConfiguration {
169
+ fn default_login_per_ip ( ) -> RateLimiterConfiguration {
123
170
RateLimiterConfiguration {
124
171
burst : NonZeroU32 :: new ( 3 ) . unwrap ( ) ,
125
172
per_second : 3.0 / 60.0 ,
@@ -133,20 +180,51 @@ fn default_login_per_account() -> RateLimiterConfiguration {
133
180
}
134
181
}
135
182
136
- #[ allow( clippy:: derivable_impls) ] // when we add some top-level ratelimiters this will not be derivable anymore
183
+ fn default_registration ( ) -> RateLimiterConfiguration {
184
+ RateLimiterConfiguration {
185
+ burst : NonZeroU32 :: new ( 3 ) . unwrap ( ) ,
186
+ per_second : 3.0 / 3600.0 ,
187
+ }
188
+ }
189
+
190
+ fn default_account_recovery_per_ip ( ) -> RateLimiterConfiguration {
191
+ RateLimiterConfiguration {
192
+ burst : NonZeroU32 :: new ( 3 ) . unwrap ( ) ,
193
+ per_second : 3.0 / 3600.0 ,
194
+ }
195
+ }
196
+
197
+ fn default_account_recovery_per_address ( ) -> RateLimiterConfiguration {
198
+ RateLimiterConfiguration {
199
+ burst : NonZeroU32 :: new ( 3 ) . unwrap ( ) ,
200
+ per_second : 1.0 / 3600.0 ,
201
+ }
202
+ }
203
+
137
204
impl Default for RateLimitingConfig {
138
205
fn default ( ) -> Self {
139
206
RateLimitingConfig {
140
207
login : LoginRateLimitingConfig :: default ( ) ,
208
+ registration : default_registration ( ) ,
209
+ account_recovery : AccountRecoveryRateLimitingConfig :: default ( ) ,
141
210
}
142
211
}
143
212
}
144
213
145
214
impl Default for LoginRateLimitingConfig {
146
215
fn default ( ) -> Self {
147
216
LoginRateLimitingConfig {
148
- per_address : default_login_per_address ( ) ,
217
+ per_ip : default_login_per_ip ( ) ,
149
218
per_account : default_login_per_account ( ) ,
150
219
}
151
220
}
152
221
}
222
+
223
+ impl Default for AccountRecoveryRateLimitingConfig {
224
+ fn default ( ) -> Self {
225
+ AccountRecoveryRateLimitingConfig {
226
+ per_ip : default_account_recovery_per_ip ( ) ,
227
+ per_address : default_account_recovery_per_address ( ) ,
228
+ }
229
+ }
230
+ }
0 commit comments