1use dashmap::DashMap;
2use std::sync::Arc;
3
4use super::AuthResult;
5use crate::config::AuthConfig;
6
7pub trait Authenticator: Send + Sync {
9 fn authenticate(&self, username: &str, password: &str) -> AuthResult<()>;
10}
11
12#[derive(Debug, Clone)]
14struct LoginAttempt {
15 remaining_attempts: u32,
17}
18
19pub type UserVerifier = Arc<dyn Fn(&str, &str) -> AuthResult<bool> + Send + Sync>;
21
22pub struct PasswordAuthenticator {
28 user_verifier: UserVerifier,
30 config: AuthConfig,
31 login_attempts: Arc<DashMap<String, LoginAttempt>>,
33}
34
35impl Clone for PasswordAuthenticator {
36 fn clone(&self) -> Self {
37 Self {
38 user_verifier: Arc::clone(&self.user_verifier),
39 config: self.config.clone(),
40 login_attempts: Arc::clone(&self.login_attempts),
41 }
42 }
43}
44
45impl PasswordAuthenticator {
46 pub fn new<F>(user_verifier: F, config: AuthConfig) -> Self
47 where
48 F: Fn(&str, &str) -> AuthResult<bool> + Send + Sync + 'static,
49 {
50 Self {
51 user_verifier: Arc::new(user_verifier),
52 config,
53 login_attempts: Arc::new(DashMap::new()),
54 }
55 }
56
57 pub fn new_default(config: AuthConfig) -> Self {
59 let default_username = config.default_username.clone();
60 let default_password = config.bootstrap_password.clone();
61
62 Self::new(
63 move |username: &str, password: &str| {
64 if username == default_username && password == default_password {
66 Ok(true)
67 } else {
68 Ok(false)
69 }
70 },
71 config,
72 )
73 }
74
75 pub fn new_with_storage<S>(storage: Arc<S>, config: AuthConfig) -> Self
82 where
83 S: crate::storage::StorageAuthOps + 'static,
84 {
85 Self::new(
86 move |username: &str, password: &str| {
87 let Some(user) = storage.get_user(username) else {
88 return Ok(false);
89 };
90 if user.is_locked {
91 return Err(crate::auth::AuthError::AccountLocked(username.to_string()));
92 }
93 Ok(user.verify_password(password))
94 },
95 config,
96 )
97 }
98
99 pub fn config(&self) -> &AuthConfig {
101 &self.config
102 }
103
104 fn record_failed_attempt(&self, username: &str) {
106 if self.config.failed_login_attempts == 0 {
108 return;
109 }
110
111 let username_key = username.to_string();
112 self.login_attempts
113 .entry(username_key.clone())
114 .or_insert(LoginAttempt {
115 remaining_attempts: self.config.failed_login_attempts,
116 });
117
118 if let Some(mut attempt) = self.login_attempts.get_mut(&username_key) {
120 if attempt.remaining_attempts > 0 {
121 attempt.remaining_attempts -= 1;
122 }
123 }
124 }
125
126 fn reset_attempts(&self, username: &str) {
128 self.login_attempts.remove(username);
129 }
130
131 fn verify_password(&self, username: &str, password: &str) -> AuthResult<bool> {
133 (self.user_verifier)(username, password)
134 }
135}
136
137impl Authenticator for PasswordAuthenticator {
138 fn authenticate(&self, username: &str, password: &str) -> AuthResult<()> {
139 use crate::auth::AuthError;
140
141 if !self.config.enable_authorize {
143 return Ok(());
144 }
145
146 if username.is_empty() || password.is_empty() {
147 return Err(AuthError::EmptyCredentials);
148 }
149
150 match self.verify_password(username, password) {
152 Ok(true) => {
153 self.reset_attempts(username);
155 Ok(())
156 }
157 Ok(false) => {
158 self.record_failed_attempt(username);
160
161 let username_key = username.to_string();
162 if let Some(attempt) = self.login_attempts.get(&username_key) {
163 if attempt.remaining_attempts > 0 {
164 return Err(AuthError::InvalidCredentials(attempt.remaining_attempts));
165 } else {
166 return Err(AuthError::MaxAttemptsExceeded);
167 }
168 }
169
170 Err(AuthError::InvalidCredentials(0))
171 }
172 Err(e) => Err(e),
173 }
174 }
175}
176
177pub struct AuthenticatorFactory;
179
180impl AuthenticatorFactory {
181 pub fn create<F>(config: &AuthConfig, user_verifier: F) -> PasswordAuthenticator
183 where
184 F: Fn(&str, &str) -> AuthResult<bool> + Send + Sync + 'static,
185 {
186 PasswordAuthenticator::new(user_verifier, config.clone())
187 }
188
189 pub fn create_default(config: &AuthConfig) -> PasswordAuthenticator {
191 PasswordAuthenticator::new_default(config.clone())
192 }
193
194 pub fn create_with_storage<S>(config: &AuthConfig, storage: Arc<S>) -> PasswordAuthenticator
196 where
197 S: crate::storage::StorageAuthOps + 'static,
198 {
199 PasswordAuthenticator::new_with_storage(storage, config.clone())
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206
207 fn create_test_config() -> AuthConfig {
208 AuthConfig {
209 enable_authorize: true,
210 failed_login_attempts: 3,
211 session_idle_timeout_secs: 3600,
212 default_username: "test".to_string(),
213 bootstrap_password: "test123".to_string(),
214 force_change_default_password: true,
215 bcrypt_cost: 12,
216 }
217 }
218
219 #[test]
220 fn test_password_authenticator_success() {
221 let config = create_test_config();
222 let auth = PasswordAuthenticator::new(|_username: &str, _password: &str| Ok(true), config);
223
224 assert!(auth.authenticate("user", "pass").is_ok());
225 }
226
227 #[test]
228 fn test_password_authenticator_failure() {
229 let config = create_test_config();
230 let auth = PasswordAuthenticator::new(|_username: &str, _password: &str| Ok(false), config);
231
232 assert!(auth.authenticate("user", "wrong_pass").is_err());
233 }
234
235 #[test]
236 fn test_password_authenticator_default() {
237 let config = AuthConfig {
238 enable_authorize: true,
239 failed_login_attempts: 0, session_idle_timeout_secs: 3600,
241 default_username: "admin".to_string(),
242 bootstrap_password: "admin123".to_string(),
243 force_change_default_password: false,
244 bcrypt_cost: 12,
245 };
246
247 let auth = PasswordAuthenticator::new_default(config);
248
249 assert!(auth.authenticate("admin", "admin123").is_ok());
251 assert!(auth.authenticate("admin", "wrong").is_err());
253 assert!(auth.authenticate("user", "admin123").is_err());
254 }
255
256 #[test]
257 fn test_login_attempt_limit() {
258 let config = AuthConfig {
259 enable_authorize: true,
260 failed_login_attempts: 2, session_idle_timeout_secs: 3600,
262 default_username: "test".to_string(),
263 bootstrap_password: "test123".to_string(),
264 force_change_default_password: false,
265 bcrypt_cost: 12,
266 };
267
268 let auth = PasswordAuthenticator::new(|_username: &str, _password: &str| Ok(false), config);
269
270 let result1 = auth.authenticate("user", "wrong");
272 assert!(result1.is_err());
273 assert!(result1
274 .unwrap_err()
275 .to_string()
276 .contains("1 attempts remaining"));
277
278 let result2 = auth.authenticate("user", "wrong");
280 assert!(result2.is_err());
281 assert!(result2
282 .unwrap_err()
283 .to_string()
284 .contains("Maximum attempts exceeded"));
285
286 let result3 = auth.authenticate("user", "wrong");
288 assert!(result3.is_err());
289 assert!(result3
290 .unwrap_err()
291 .to_string()
292 .contains("Maximum attempts exceeded"));
293 }
294
295 #[test]
296 fn test_successful_login_resets_attempts() {
297 use std::sync::atomic::{AtomicBool, Ordering};
299 use std::sync::Arc;
300
301 let config = AuthConfig {
302 enable_authorize: true,
303 failed_login_attempts: 2,
304 session_idle_timeout_secs: 3600,
305 default_username: "test".to_string(),
306 bootstrap_password: "test123".to_string(),
307 force_change_default_password: false,
308 bcrypt_cost: 12,
309 };
310
311 let success = Arc::new(AtomicBool::new(false));
312 let success_clone = success.clone();
313
314 let auth = PasswordAuthenticator::new(
315 move |_username: &str, _password: &str| {
316 if success_clone.load(Ordering::SeqCst) {
317 Ok(true)
318 } else {
319 Ok(false)
320 }
321 },
322 config,
323 );
324
325 assert!(auth.authenticate("user", "wrong").is_err());
327
328 success.store(true, Ordering::SeqCst);
330 assert!(auth.authenticate("user", "correct").is_ok());
331
332 success.store(false, Ordering::SeqCst);
334 assert!(auth.authenticate("user", "wrong").is_err());
335 assert!(auth.authenticate("user", "wrong").is_err());
337 }
338
339 #[test]
340 fn test_authenticator_factory() {
341 let config = AuthConfig {
342 enable_authorize: true,
343 failed_login_attempts: 0,
344 session_idle_timeout_secs: 3600,
345 default_username: "test".to_string(),
346 bootstrap_password: "test123".to_string(),
347 force_change_default_password: false,
348 bcrypt_cost: 12,
349 };
350
351 let _auth =
352 AuthenticatorFactory::create(&config, |_username: &str, _password: &str| Ok(true));
353 let _auth_default = AuthenticatorFactory::create_default(&config);
356 }
358}