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) -> Result<Option<User>, UserDetailsError>;
121
122 async fn user_exists(&self, username: &str) -> Result<bool, UserDetailsError> {
124 Ok(self.load_user_by_username(username).await?.is_some())
125 }
126}
127
128#[async_trait]
152pub trait UserDetailsManager: UserDetailsService {
153 async fn create_user(&self, user: &User) -> Result<(), UserDetailsError>;
155
156 async fn update_user(&self, user: &User) -> Result<(), UserDetailsError>;
158
159 async fn delete_user(&self, username: &str) -> Result<(), UserDetailsError>;
161
162 async fn change_password(
169 &self,
170 username: &str,
171 old_password: &str,
172 new_password: &str,
173 ) -> Result<(), UserDetailsError>;
174}
175
176#[derive(Clone)]
184pub struct InMemoryUserDetailsService {
185 users: Arc<RwLock<HashMap<String, User>>>,
186}
187
188impl Default for InMemoryUserDetailsService {
189 fn default() -> Self {
190 Self::new()
191 }
192}
193
194impl InMemoryUserDetailsService {
195 pub fn new() -> Self {
197 Self {
198 users: Arc::new(RwLock::new(HashMap::new())),
199 }
200 }
201
202 pub async fn add_user(&self, user: User) {
204 let mut users = self.users.write().await;
205 users.insert(user.get_username().to_string(), user);
206 }
207
208 pub async fn add_users(&self, users: Vec<User>) {
210 let mut store = self.users.write().await;
211 for user in users {
212 store.insert(user.get_username().to_string(), user);
213 }
214 }
215}
216
217#[async_trait]
218impl UserDetailsService for InMemoryUserDetailsService {
219 async fn load_user_by_username(&self, username: &str) -> Result<Option<User>, UserDetailsError> {
220 let users = self.users.read().await;
221 Ok(users.get(username).cloned())
222 }
223}
224
225#[async_trait]
226impl UserDetailsManager for InMemoryUserDetailsService {
227 async fn create_user(&self, user: &User) -> Result<(), UserDetailsError> {
228 let mut users = self.users.write().await;
229 let username = user.get_username().to_string();
230 if users.contains_key(&username) {
231 return Err(UserDetailsError::AlreadyExists);
232 }
233 users.insert(username, user.clone());
234 Ok(())
235 }
236
237 async fn update_user(&self, user: &User) -> Result<(), UserDetailsError> {
238 let mut users = self.users.write().await;
239 let username = user.get_username().to_string();
240 if !users.contains_key(&username) {
241 return Err(UserDetailsError::NotFound);
242 }
243 users.insert(username, user.clone());
244 Ok(())
245 }
246
247 async fn delete_user(&self, username: &str) -> Result<(), UserDetailsError> {
248 let mut users = self.users.write().await;
249 if users.remove(username).is_none() {
250 return Err(UserDetailsError::NotFound);
251 }
252 Ok(())
253 }
254
255 async fn change_password(
256 &self,
257 username: &str,
258 _old_password: &str,
259 new_password: &str,
260 ) -> Result<(), UserDetailsError> {
261 let mut users = self.users.write().await;
262 match users.get_mut(username) {
263 Some(user) => {
264 let updated = User::new(user.get_username().to_string(), new_password.to_string())
266 .roles(user.get_roles())
267 .authorities(user.get_authorities());
268 *user = updated;
269 Ok(())
270 }
271 None => Err(UserDetailsError::NotFound),
272 }
273 }
274}
275
276struct CachedUser {
282 user: User,
283 cached_at: Instant,
284}
285
286pub struct CachingUserDetailsService<S>
296where
297 S: UserDetailsService,
298{
299 inner: S,
300 cache: Arc<RwLock<HashMap<String, CachedUser>>>,
301 ttl: Duration,
302}
303
304impl<S> CachingUserDetailsService<S>
305where
306 S: UserDetailsService,
307{
308 pub fn new(inner: S) -> Self {
310 Self {
311 inner,
312 cache: Arc::new(RwLock::new(HashMap::new())),
313 ttl: Duration::from_secs(300),
314 }
315 }
316
317 pub fn ttl(mut self, ttl: Duration) -> Self {
319 self.ttl = ttl;
320 self
321 }
322
323 pub async fn clear_cache(&self) {
325 let mut cache = self.cache.write().await;
326 cache.clear();
327 }
328
329 pub async fn invalidate(&self, username: &str) {
331 let mut cache = self.cache.write().await;
332 cache.remove(username);
333 }
334
335 fn is_valid(&self, entry: &CachedUser) -> bool {
337 entry.cached_at.elapsed() < self.ttl
338 }
339}
340
341#[async_trait]
342impl<S> UserDetailsService for CachingUserDetailsService<S>
343where
344 S: UserDetailsService + Send + Sync,
345{
346 async fn load_user_by_username(&self, username: &str) -> Result<Option<User>, UserDetailsError> {
347 {
349 let cache = self.cache.read().await;
350 if let Some(cached) = cache.get(username) {
351 if self.is_valid(cached) {
352 return Ok(Some(cached.user.clone()));
353 }
354 }
355 }
356
357 let result = self.inner.load_user_by_username(username).await?;
359
360 if let Some(ref user) = result {
362 let mut cache = self.cache.write().await;
363 cache.insert(
364 username.to_string(),
365 CachedUser {
366 user: user.clone(),
367 cached_at: Instant::now(),
368 },
369 );
370 }
371
372 Ok(result)
373 }
374}
375
376#[derive(Clone)]
396pub struct UserDetailsAuthenticator<S, E>
397where
398 S: UserDetailsService + Clone,
399 E: PasswordEncoder + Clone,
400{
401 service: Arc<S>,
402 encoder: Arc<E>,
403}
404
405impl<S, E> UserDetailsAuthenticator<S, E>
406where
407 S: UserDetailsService + Clone,
408 E: PasswordEncoder + Clone,
409{
410 pub fn new(service: S, encoder: E) -> Self {
412 Self {
413 service: Arc::new(service),
414 encoder: Arc::new(encoder),
415 }
416 }
417
418 pub async fn authenticate(
420 &self,
421 username: &str,
422 password: &str,
423 ) -> Result<User, UserDetailsError> {
424 let user = self
426 .service
427 .load_user_by_username(username)
428 .await?
429 .ok_or(UserDetailsError::NotFound)?;
430
431 if self.encoder.matches(password, user.get_password()) {
433 Ok(user)
434 } else {
435 Err(UserDetailsError::InvalidCredentials)
436 }
437 }
438
439 pub fn service(&self) -> &S {
441 &self.service
442 }
443
444 pub fn encoder(&self) -> &E {
446 &self.encoder
447 }
448}
449
450#[cfg(test)]
451mod tests {
452 use super::*;
453
454 fn test_user() -> User {
455 User::new("testuser".to_string(), "password".to_string())
456 .roles(&["USER".into()])
457 .authorities(&["read".into()])
458 }
459
460 #[tokio::test]
461 async fn test_in_memory_service() {
462 let service = InMemoryUserDetailsService::new();
463 let user = test_user();
464
465 service.add_user(user.clone()).await;
467
468 let loaded = service.load_user_by_username("testuser").await.unwrap();
470 assert!(loaded.is_some());
471 assert_eq!(loaded.unwrap().get_username(), "testuser");
472
473 assert!(service.user_exists("testuser").await.unwrap());
475 assert!(!service.user_exists("unknown").await.unwrap());
476 }
477
478 #[tokio::test]
479 async fn test_in_memory_manager() {
480 let service = InMemoryUserDetailsService::new();
481 let user = test_user();
482
483 service.create_user(&user).await.unwrap();
485 assert!(service.user_exists("testuser").await.unwrap());
486
487 let result = service.create_user(&user).await;
489 assert!(matches!(result, Err(UserDetailsError::AlreadyExists)));
490
491 let updated = User::new("testuser".to_string(), "newpass".to_string())
493 .roles(&["ADMIN".into()]);
494 service.update_user(&updated).await.unwrap();
495
496 let loaded = service.load_user_by_username("testuser").await.unwrap().unwrap();
497 assert!(loaded.has_role("ADMIN"));
498
499 service.delete_user("testuser").await.unwrap();
501 assert!(!service.user_exists("testuser").await.unwrap());
502
503 let result = service.delete_user("testuser").await;
505 assert!(matches!(result, Err(UserDetailsError::NotFound)));
506 }
507
508 #[tokio::test]
509 async fn test_caching_service() {
510 let inner = InMemoryUserDetailsService::new();
511 inner.add_user(test_user()).await;
512
513 let cached = CachingUserDetailsService::new(inner)
514 .ttl(Duration::from_secs(60));
515
516 let user1 = cached.load_user_by_username("testuser").await.unwrap();
518 assert!(user1.is_some());
519
520 let user2 = cached.load_user_by_username("testuser").await.unwrap();
522 assert!(user2.is_some());
523
524 cached.invalidate("testuser").await;
526
527 let user3 = cached.load_user_by_username("testuser").await.unwrap();
529 assert!(user3.is_some());
530 }
531}