auth_framework/auth_modular/
user_manager.rs

1//! User management module
2
3use crate::errors::{AuthError, Result};
4use crate::storage::AuthStorage;
5use std::collections::HashMap;
6use std::sync::Arc;
7use tracing::{debug, info};
8
9/// User information structure
10#[derive(Debug, Clone)]
11pub struct UserInfo {
12    /// User ID
13    pub id: String,
14
15    /// Username
16    pub username: String,
17
18    /// Email address
19    pub email: Option<String>,
20
21    /// Display name
22    pub name: Option<String>,
23
24    /// User roles
25    pub roles: Vec<String>,
26
27    /// Whether the user is active
28    pub active: bool,
29
30    /// Additional user attributes
31    pub attributes: HashMap<String, serde_json::Value>,
32}
33
34/// User manager for handling user operations
35pub struct UserManager {
36    storage: Arc<dyn AuthStorage>,
37}
38
39impl UserManager {
40    /// Create a new user manager
41    pub fn new(storage: Arc<dyn AuthStorage>) -> Self {
42        Self { storage }
43    }
44
45    /// Create API key for a user
46    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        // Generate a secure API key
54        let api_key = format!("ak_{}", crate::utils::crypto::generate_token(32));
55
56        // Store API key metadata
57        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    /// Validate API key and return user information
73    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                // Check expiration
82                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                // Return user information
93                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    /// Revoke API key
111    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    /// Validate username format
125    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    /// Validate display name format
138    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    /// Validate password strength using security policy
149    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        // Consider Medium, Strong, and VeryStrong passwords as valid
155        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    /// Validate user input for security
171    pub async fn validate_user_input(&self, input: &str) -> Result<bool> {
172        debug!("Validating user input");
173
174        // Comprehensive security validation
175        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("${")  // Template injection
182            && !input.contains("{{")  // Template injection
183            && !input.contains("'}") && !input.contains("'}")  // Template injection
184            && !input.contains("'; DROP") && !input.contains("' DROP") // SQL injection
185            && !input.contains("; DROP") && !input.contains(";DROP") // SQL injection
186            && !input.contains("--") // SQL comments
187            && !input.contains("../") // Path traversal
188            && !input.contains("..\\") // Path traversal (Windows)
189            && !input.contains('\0') // Null byte injection
190            && !input.contains("%00") // URL encoded null byte
191            && !input.contains("jndi:") // LDAP injection
192            && !input.contains("%3C") && !input.contains("%3E") // URL encoded < >
193            && input.len() <= 1000;
194
195        Ok(is_valid)
196    }
197
198    /// Map user attribute
199    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    /// Get user attribute
218    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            // Return some default values for common attributes for demo purposes
232            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    /// Get user information by ID
242    pub async fn get_user_info(&self, user_id: &str) -> Result<UserInfo> {
243        debug!("Getting user info for '{}'", user_id);
244
245        // For now, return a basic user info structure
246        // In a real implementation, this would query a user database
247        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    /// Check if user exists
259    pub async fn user_exists(&self, user_id: &str) -> Result<bool> {
260        debug!("Checking if user '{}' exists", user_id);
261
262        // For now, assume all non-empty user IDs exist
263        // In a real implementation, this would check a user database
264        Ok(!user_id.is_empty())
265    }
266}
267
268