reinhardt_auth/
default_user_manager.rs1#![allow(deprecated)]
4#[cfg(feature = "argon2-hasher")]
5use crate::BaseUser;
6#[cfg(feature = "argon2-hasher")]
7use crate::base_user_manager::BaseUserManager;
8#[cfg(feature = "argon2-hasher")]
9use crate::default_user::DefaultUser;
10#[cfg(feature = "argon2-hasher")]
11use async_trait::async_trait;
12#[cfg(feature = "argon2-hasher")]
13use chrono::Utc;
14#[cfg(feature = "argon2-hasher")]
15use reinhardt_core::exception::Error;
16
17#[cfg(feature = "argon2-hasher")]
18type Result<T> = std::result::Result<T, Error>;
19#[cfg(feature = "argon2-hasher")]
20use serde_json::Value;
21#[cfg(feature = "argon2-hasher")]
22use std::collections::HashMap;
23#[cfg(feature = "argon2-hasher")]
24use std::sync::{Arc, RwLock};
25#[cfg(feature = "argon2-hasher")]
26use uuid::Uuid;
27
28#[cfg(feature = "argon2-hasher")]
103pub struct DefaultUserManager {
104 users: Arc<RwLock<HashMap<Uuid, DefaultUser>>>,
105}
106
107#[cfg(feature = "argon2-hasher")]
108impl DefaultUserManager {
109 pub fn new() -> Self {
111 Self {
112 users: Arc::new(RwLock::new(HashMap::new())),
113 }
114 }
115
116 pub fn get_by_id(&self, id: Uuid) -> Option<DefaultUser> {
118 let users = self.users.read().unwrap_or_else(|e| e.into_inner());
119 users.get(&id).cloned()
120 }
121
122 pub fn get_by_username(&self, username: &str) -> Option<DefaultUser> {
124 let users = self.users.read().unwrap_or_else(|e| e.into_inner());
125 users.values().find(|u| u.username == username).cloned()
126 }
127
128 pub fn list_all(&self) -> Vec<DefaultUser> {
130 let users = self.users.read().unwrap_or_else(|e| e.into_inner());
131 users.values().cloned().collect()
132 }
133}
134
135#[cfg(feature = "argon2-hasher")]
136impl Default for DefaultUserManager {
137 fn default() -> Self {
138 Self::new()
139 }
140}
141
142#[cfg(feature = "argon2-hasher")]
143#[async_trait]
144impl BaseUserManager<DefaultUser> for DefaultUserManager {
145 async fn create_user(
146 &mut self,
147 username: &str,
148 password: Option<&str>,
149 extra: HashMap<String, Value>,
150 ) -> Result<DefaultUser> {
151 if self.get_by_username(username).is_some() {
153 return Err(reinhardt_core::exception::Error::Validation(format!(
154 "Username '{}' already exists",
155 username
156 )));
157 }
158
159 let mut user = DefaultUser {
161 id: Uuid::new_v4(),
162 username: username.to_string(),
163 email: String::new(),
164 first_name: String::new(),
165 last_name: String::new(),
166 password_hash: None,
167 last_login: None,
168 is_active: true,
169 is_staff: false,
170 is_superuser: false,
171 date_joined: Utc::now(),
172 user_permissions: Vec::new(),
173 groups: Vec::new(),
174 };
175
176 if let Some(email) = extra.get("email")
178 && let Some(email_str) = email.as_str()
179 {
180 user.email = Self::normalize_email(email_str);
181 }
182
183 if let Some(first_name) = extra.get("first_name")
184 && let Some(name) = first_name.as_str()
185 {
186 user.first_name = name.to_string();
187 }
188
189 if let Some(last_name) = extra.get("last_name")
190 && let Some(name) = last_name.as_str()
191 {
192 user.last_name = name.to_string();
193 }
194
195 if let Some(is_active) = extra.get("is_active")
196 && let Some(active) = is_active.as_bool()
197 {
198 user.is_active = active;
199 }
200
201 if let Some(pwd) = password {
203 user.set_password(pwd)?;
204 }
205
206 let mut users = self.users.write().unwrap_or_else(|e| e.into_inner());
208 users.insert(user.id, user.clone());
209
210 Ok(user)
211 }
212
213 async fn create_superuser(
214 &mut self,
215 username: &str,
216 password: Option<&str>,
217 extra: HashMap<String, Value>,
218 ) -> Result<DefaultUser> {
219 let mut user = self.create_user(username, password, extra).await?;
221
222 user.is_staff = true;
224 user.is_superuser = true;
225
226 let mut users = self.users.write().unwrap_or_else(|e| e.into_inner());
228 users.insert(user.id, user.clone());
229
230 Ok(user)
231 }
232}
233
234#[cfg(feature = "argon2-hasher")]
235#[cfg(test)]
236mod tests {
237 use super::*;
238 use rstest::rstest;
239 use std::collections::HashMap;
240
241 #[rstest]
242 #[tokio::test]
243 async fn test_rwlock_poison_recovery_default_user_manager() {
244 let mut manager = DefaultUserManager::new();
246 let user = manager
247 .create_user("pre_poison", Some("password123"), HashMap::new())
248 .await
249 .unwrap();
250 let user_id = user.id;
251
252 let users_clone = Arc::clone(&manager.users);
254 let _ = std::thread::spawn(move || {
255 let _guard = users_clone.write().unwrap();
256 panic!("intentional panic to poison lock");
257 })
258 .join();
259
260 let found = manager.get_by_id(user_id);
262 assert!(found.is_some());
263 assert_eq!(found.unwrap().username, "pre_poison");
264
265 let found_by_name = manager.get_by_username("pre_poison");
266 assert!(found_by_name.is_some());
267
268 let all_users = manager.list_all();
269 assert_eq!(all_users.len(), 1);
270
271 let new_user = manager
273 .create_user("post_poison", Some("password456"), HashMap::new())
274 .await
275 .unwrap();
276 assert_eq!(new_user.username, "post_poison");
277 assert_eq!(manager.list_all().len(), 2);
278 }
279}