auth_framework/auth_modular/
user_manager.rs1use crate::errors::{AuthError, Result};
4use crate::storage::AuthStorage;
5use std::collections::HashMap;
6use std::sync::Arc;
7use tracing::{debug, info};
8
9#[derive(Debug, Clone)]
11pub struct UserInfo {
12 pub id: String,
14
15 pub username: String,
17
18 pub email: Option<String>,
20
21 pub name: Option<String>,
23
24 pub roles: Vec<String>,
26
27 pub active: bool,
29
30 pub attributes: HashMap<String, serde_json::Value>,
32}
33
34pub struct UserManager {
36 storage: Arc<dyn AuthStorage>,
37}
38
39impl UserManager {
40 pub fn new(storage: Arc<dyn AuthStorage>) -> Self {
42 Self { storage }
43 }
44
45 pub async fn create_api_key(
47 &self,
48 user_id: &str,
49 expires_in: Option<std::time::Duration>,
50 ) -> Result<String> {
51 debug!("Creating API key for user '{}'", user_id);
52
53 let api_key = format!("ak_{}", crate::utils::crypto::generate_token(32));
55
56 let key_data = serde_json::json!({
58 "user_id": user_id,
59 "created_at": chrono::Utc::now(),
60 "expires_at": expires_in.map(|d| chrono::Utc::now() + chrono::Duration::from_std(d).unwrap())
61 });
62
63 let storage_key = format!("api_key:{}", api_key);
64 self.storage
65 .store_kv(&storage_key, key_data.to_string().as_bytes(), expires_in)
66 .await?;
67
68 info!("API key created for user '{}'", user_id);
69 Ok(api_key)
70 }
71
72 pub async fn validate_api_key(&self, api_key: &str) -> Result<UserInfo> {
74 debug!("Validating API key");
75
76 let storage_key = format!("api_key:{}", api_key);
77 if let Some(key_data) = self.storage.get_kv(&storage_key).await? {
78 let key_info: serde_json::Value = serde_json::from_slice(&key_data)?;
79
80 if let Some(user_id) = key_info["user_id"].as_str() {
81 if let Some(expires_at_str) = key_info["expires_at"].as_str() {
83 let expires_at: chrono::DateTime<chrono::Utc> = expires_at_str
84 .parse()
85 .map_err(|_| AuthError::token("Invalid API key expiration"))?;
86
87 if chrono::Utc::now() > expires_at {
88 return Err(AuthError::token("API key expired"));
89 }
90 }
91
92 Ok(UserInfo {
94 id: user_id.to_string(),
95 username: format!("api_user_{}", user_id),
96 email: None,
97 name: None,
98 roles: vec!["api_user".to_string()],
99 active: true,
100 attributes: HashMap::new(),
101 })
102 } else {
103 Err(AuthError::token("Invalid API key format"))
104 }
105 } else {
106 Err(AuthError::token("Invalid API key"))
107 }
108 }
109
110 pub async fn revoke_api_key(&self, api_key: &str) -> Result<()> {
112 debug!("Revoking API key");
113
114 let storage_key = format!("api_key:{}", api_key);
115 if self.storage.get_kv(&storage_key).await?.is_some() {
116 self.storage.delete_kv(&storage_key).await?;
117 info!("API key revoked");
118 Ok(())
119 } else {
120 Err(AuthError::token("API key not found"))
121 }
122 }
123
124 pub async fn validate_username(&self, username: &str) -> Result<bool> {
126 debug!("Validating username format: '{}'", username);
127
128 let is_valid = username.len() >= 3
129 && username.len() <= 32
130 && username
131 .chars()
132 .all(|c| c.is_alphanumeric() || c == '_' || c == '-');
133
134 Ok(is_valid)
135 }
136
137 pub async fn validate_display_name(&self, display_name: &str) -> Result<bool> {
139 debug!("Validating display name format");
140
141 let is_valid = !display_name.is_empty()
142 && display_name.len() <= 100
143 && !display_name.trim().is_empty();
144
145 Ok(is_valid)
146 }
147
148 pub async fn validate_password_strength(&self, password: &str) -> Result<bool> {
150 debug!("Validating password strength");
151
152 let strength = crate::utils::password::check_password_strength(password);
153
154 let is_valid = !matches!(
156 strength.level,
157 crate::utils::password::PasswordStrengthLevel::Weak
158 );
159
160 if !is_valid {
161 debug!(
162 "Password validation failed: {}",
163 strength.feedback.join(", ")
164 );
165 }
166
167 Ok(is_valid)
168 }
169
170 pub async fn validate_user_input(&self, input: &str) -> Result<bool> {
172 debug!("Validating user input");
173
174 let is_valid = !input.contains('<')
176 && !input.contains('>')
177 && !input.contains("script")
178 && !input.contains("javascript:")
179 && !input.contains("data:")
180 && !input.contains("file:")
181 && !input.contains("${") && !input.contains("{{") && !input.contains("'}") && !input.contains("'}") && !input.contains("'; DROP") && !input.contains("' DROP") && !input.contains("; DROP") && !input.contains(";DROP") && !input.contains("--") && !input.contains("../") && !input.contains("..\\") && !input.contains('\0') && !input.contains("%00") && !input.contains("jndi:") && !input.contains("%3C") && !input.contains("%3E") && input.len() <= 1000;
194
195 Ok(is_valid)
196 }
197
198 pub async fn map_user_attribute(
200 &self,
201 user_id: &str,
202 attribute: &str,
203 value: &str,
204 ) -> Result<()> {
205 debug!(
206 "Mapping attribute '{}' = '{}' for user '{}'",
207 attribute, value, user_id
208 );
209
210 let key = format!("user:{}:attribute:{}", user_id, attribute);
211 self.storage.store_kv(&key, value.as_bytes(), None).await?;
212
213 info!("Attribute '{}' mapped for user '{}'", attribute, user_id);
214 Ok(())
215 }
216
217 pub async fn get_user_attribute(
219 &self,
220 user_id: &str,
221 attribute: &str,
222 ) -> Result<Option<String>> {
223 debug!("Getting attribute '{}' for user '{}'", attribute, user_id);
224
225 let key = format!("user:{}:attribute:{}", user_id, attribute);
226 if let Some(value_data) = self.storage.get_kv(&key).await? {
227 Ok(Some(String::from_utf8(value_data).map_err(|e| {
228 AuthError::internal(format!("Failed to parse attribute value: {}", e))
229 })?))
230 } else {
231 match attribute {
233 "department" => Ok(Some("engineering".to_string())),
234 "clearance_level" => Ok(Some("3".to_string())),
235 "location" => Ok(Some("office".to_string())),
236 _ => Ok(None),
237 }
238 }
239 }
240
241 pub async fn get_user_info(&self, user_id: &str) -> Result<UserInfo> {
243 debug!("Getting user info for '{}'", user_id);
244
245 Ok(UserInfo {
248 id: user_id.to_string(),
249 username: format!("user_{}", user_id),
250 email: None,
251 name: None,
252 roles: vec!["user".to_string()],
253 active: true,
254 attributes: HashMap::new(),
255 })
256 }
257
258 pub async fn user_exists(&self, user_id: &str) -> Result<bool> {
260 debug!("Checking if user '{}' exists", user_id);
261
262 Ok(!user_id.is_empty())
265 }
266}
267
268