actix_security_core/http/security/
user_details.rs1use crate::http::security::crypto::PasswordEncoder;
31use crate::http::security::User;
32use async_trait::async_trait;
33use std::collections::HashMap;
34use std::sync::Arc;
35use std::time::{Duration, Instant};
36use tokio::sync::RwLock;
37
38#[derive(Debug)]
44pub enum UserDetailsError {
45 NotFound,
47 AlreadyExists,
49 InvalidCredentials,
51 AccountDisabled,
53 AccountLocked,
55 AccountExpired,
57 CredentialsExpired,
59 StorageError(String),
61 Other(String),
63}
64
65impl std::fmt::Display for UserDetailsError {
66 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 match self {
68 UserDetailsError::NotFound => write!(f, "User not found"),
69 UserDetailsError::AlreadyExists => write!(f, "User already exists"),
70 UserDetailsError::InvalidCredentials => write!(f, "Invalid credentials"),
71 UserDetailsError::AccountDisabled => write!(f, "Account is disabled"),
72 UserDetailsError::AccountLocked => write!(f, "Account is locked"),
73 UserDetailsError::AccountExpired => write!(f, "Account is expired"),
74 UserDetailsError::CredentialsExpired => write!(f, "Credentials are expired"),
75 UserDetailsError::StorageError(e) => write!(f, "Storage error: {}", e),
76 UserDetailsError::Other(e) => write!(f, "Error: {}", e),
77 }
78 }
79}
80
81impl std::error::Error for UserDetailsError {}
82
83#[async_trait]
115pub trait UserDetailsService: Send + Sync {
116 async fn load_user_by_username(&self, username: &str)
121 -> Result<Option<User>, UserDetailsError>;
122
123 async fn user_exists(&self, username: &str) -> Result<bool, UserDetailsError> {
125 Ok(self.load_user_by_username(username).await?.is_some())
126 }
127}
128
129#[async_trait]
153pub trait UserDetailsManager: UserDetailsService {
154 async fn create_user(&self, user: &User) -> Result<(), UserDetailsError>;
156
157 async fn update_user(&self, user: &User) -> Result<(), UserDetailsError>;
159
160 async fn delete_user(&self, username: &str) -> Result<(), UserDetailsError>;
162
163 async fn change_password(
170 &self,
171 username: &str,
172 old_password: &str,
173 new_password: &str,
174 ) -> Result<(), UserDetailsError>;
175}
176
177#[derive(Clone)]
185pub struct InMemoryUserDetailsService {
186 users: Arc<RwLock<HashMap<String, User>>>,
187}
188
189impl Default for InMemoryUserDetailsService {
190 fn default() -> Self {
191 Self::new()
192 }
193}
194
195impl InMemoryUserDetailsService {
196 pub fn new() -> Self {
198 Self {
199 users: Arc::new(RwLock::new(HashMap::new())),
200 }
201 }
202
203 pub async fn add_user(&self, user: User) {
205 let mut users = self.users.write().await;
206 users.insert(user.get_username().to_string(), user);
207 }
208
209 pub async fn add_users(&self, users: Vec<User>) {
211 let mut store = self.users.write().await;
212 for user in users {
213 store.insert(user.get_username().to_string(), user);
214 }
215 }
216}
217
218#[async_trait]
219impl UserDetailsService for InMemoryUserDetailsService {
220 async fn load_user_by_username(
221 &self,
222 username: &str,
223 ) -> Result<Option<User>, UserDetailsError> {
224 let users = self.users.read().await;
225 Ok(users.get(username).cloned())
226 }
227}
228
229#[async_trait]
230impl UserDetailsManager for InMemoryUserDetailsService {
231 async fn create_user(&self, user: &User) -> Result<(), UserDetailsError> {
232 let mut users = self.users.write().await;
233 let username = user.get_username().to_string();
234 if users.contains_key(&username) {
235 return Err(UserDetailsError::AlreadyExists);
236 }
237 users.insert(username, user.clone());
238 Ok(())
239 }
240
241 async fn update_user(&self, user: &User) -> Result<(), UserDetailsError> {
242 let mut users = self.users.write().await;
243 let username = user.get_username().to_string();
244 if !users.contains_key(&username) {
245 return Err(UserDetailsError::NotFound);
246 }
247 users.insert(username, user.clone());
248 Ok(())
249 }
250
251 async fn delete_user(&self, username: &str) -> Result<(), UserDetailsError> {
252 let mut users = self.users.write().await;
253 if users.remove(username).is_none() {
254 return Err(UserDetailsError::NotFound);
255 }
256 Ok(())
257 }
258
259 async fn change_password(
260 &self,
261 username: &str,
262 _old_password: &str,
263 new_password: &str,
264 ) -> Result<(), UserDetailsError> {
265 let mut users = self.users.write().await;
266 match users.get_mut(username) {
267 Some(user) => {
268 let updated = User::new(user.get_username().to_string(), new_password.to_string())
270 .roles(user.get_roles())
271 .authorities(user.get_authorities());
272 *user = updated;
273 Ok(())
274 }
275 None => Err(UserDetailsError::NotFound),
276 }
277 }
278}
279
280struct CachedUser {
286 user: User,
287 cached_at: Instant,
288}
289
290pub struct CachingUserDetailsService<S>
300where
301 S: UserDetailsService,
302{
303 inner: S,
304 cache: Arc<RwLock<HashMap<String, CachedUser>>>,
305 ttl: Duration,
306}
307
308impl<S> CachingUserDetailsService<S>
309where
310 S: UserDetailsService,
311{
312 pub fn new(inner: S) -> Self {
314 Self {
315 inner,
316 cache: Arc::new(RwLock::new(HashMap::new())),
317 ttl: Duration::from_secs(300),
318 }
319 }
320
321 pub fn ttl(mut self, ttl: Duration) -> Self {
323 self.ttl = ttl;
324 self
325 }
326
327 pub async fn clear_cache(&self) {
329 let mut cache = self.cache.write().await;
330 cache.clear();
331 }
332
333 pub async fn invalidate(&self, username: &str) {
335 let mut cache = self.cache.write().await;
336 cache.remove(username);
337 }
338
339 fn is_valid(&self, entry: &CachedUser) -> bool {
341 entry.cached_at.elapsed() < self.ttl
342 }
343}
344
345#[async_trait]
346impl<S> UserDetailsService for CachingUserDetailsService<S>
347where
348 S: UserDetailsService + Send + Sync,
349{
350 async fn load_user_by_username(
351 &self,
352 username: &str,
353 ) -> Result<Option<User>, UserDetailsError> {
354 {
356 let cache = self.cache.read().await;
357 if let Some(cached) = cache.get(username) {
358 if self.is_valid(cached) {
359 return Ok(Some(cached.user.clone()));
360 }
361 }
362 }
363
364 let result = self.inner.load_user_by_username(username).await?;
366
367 if let Some(ref user) = result {
369 let mut cache = self.cache.write().await;
370 cache.insert(
371 username.to_string(),
372 CachedUser {
373 user: user.clone(),
374 cached_at: Instant::now(),
375 },
376 );
377 }
378
379 Ok(result)
380 }
381}
382
383#[derive(Clone)]
403pub struct UserDetailsAuthenticator<S, E>
404where
405 S: UserDetailsService + Clone,
406 E: PasswordEncoder + Clone,
407{
408 service: Arc<S>,
409 encoder: Arc<E>,
410}
411
412impl<S, E> UserDetailsAuthenticator<S, E>
413where
414 S: UserDetailsService + Clone,
415 E: PasswordEncoder + Clone,
416{
417 pub fn new(service: S, encoder: E) -> Self {
419 Self {
420 service: Arc::new(service),
421 encoder: Arc::new(encoder),
422 }
423 }
424
425 pub async fn authenticate(
427 &self,
428 username: &str,
429 password: &str,
430 ) -> Result<User, UserDetailsError> {
431 let user = self
433 .service
434 .load_user_by_username(username)
435 .await?
436 .ok_or(UserDetailsError::NotFound)?;
437
438 if self.encoder.matches(password, user.get_password()) {
440 Ok(user)
441 } else {
442 Err(UserDetailsError::InvalidCredentials)
443 }
444 }
445
446 pub fn service(&self) -> &S {
448 &self.service
449 }
450
451 pub fn encoder(&self) -> &E {
453 &self.encoder
454 }
455}
456
457#[cfg(test)]
458mod tests {
459 use super::*;
460
461 fn test_user() -> User {
462 User::new("testuser".to_string(), "password".to_string())
463 .roles(&["USER".into()])
464 .authorities(&["read".into()])
465 }
466
467 #[tokio::test]
468 async fn test_in_memory_service() {
469 let service = InMemoryUserDetailsService::new();
470 let user = test_user();
471
472 service.add_user(user.clone()).await;
474
475 let loaded = service.load_user_by_username("testuser").await.unwrap();
477 assert!(loaded.is_some());
478 assert_eq!(loaded.unwrap().get_username(), "testuser");
479
480 assert!(service.user_exists("testuser").await.unwrap());
482 assert!(!service.user_exists("unknown").await.unwrap());
483 }
484
485 #[tokio::test]
486 async fn test_in_memory_manager() {
487 let service = InMemoryUserDetailsService::new();
488 let user = test_user();
489
490 service.create_user(&user).await.unwrap();
492 assert!(service.user_exists("testuser").await.unwrap());
493
494 let result = service.create_user(&user).await;
496 assert!(matches!(result, Err(UserDetailsError::AlreadyExists)));
497
498 let updated =
500 User::new("testuser".to_string(), "newpass".to_string()).roles(&["ADMIN".into()]);
501 service.update_user(&updated).await.unwrap();
502
503 let loaded = service
504 .load_user_by_username("testuser")
505 .await
506 .unwrap()
507 .unwrap();
508 assert!(loaded.has_role("ADMIN"));
509
510 service.delete_user("testuser").await.unwrap();
512 assert!(!service.user_exists("testuser").await.unwrap());
513
514 let result = service.delete_user("testuser").await;
516 assert!(matches!(result, Err(UserDetailsError::NotFound)));
517 }
518
519 #[tokio::test]
520 async fn test_caching_service() {
521 let inner = InMemoryUserDetailsService::new();
522 inner.add_user(test_user()).await;
523
524 let cached = CachingUserDetailsService::new(inner).ttl(Duration::from_secs(60));
525
526 let user1 = cached.load_user_by_username("testuser").await.unwrap();
528 assert!(user1.is_some());
529
530 let user2 = cached.load_user_by_username("testuser").await.unwrap();
532 assert!(user2.is_some());
533
534 cached.invalidate("testuser").await;
536
537 let user3 = cached.load_user_by_username("testuser").await.unwrap();
539 assert!(user3.is_some());
540 }
541}