pulseengine_mcp_auth/
models.rs

1//! Authentication models
2
3use crate::crypto::hashing::Salt;
4use chrono::{DateTime, Utc};
5use serde::{Deserialize, Serialize};
6use std::fmt;
7
8/// API key for authentication with comprehensive metadata
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct ApiKey {
11    /// Unique key identifier (format: lmcp_{role}_{timestamp}_{random})
12    pub id: String,
13    /// Human-readable name/description
14    pub name: String,
15    /// The actual secret token used for authentication
16    pub key: String,
17    /// Secure hash of the secret token (for storage)
18    pub secret_hash: Option<String>,
19    /// Salt used for hashing the secret token
20    pub salt: Option<Salt>,
21    /// Role-based permissions
22    pub role: Role,
23    /// Creation timestamp
24    pub created_at: DateTime<Utc>,
25    /// Optional expiration timestamp
26    pub expires_at: Option<DateTime<Utc>>,
27    /// Last time this key was used
28    pub last_used: Option<DateTime<Utc>>,
29    /// IP address whitelist (empty = all IPs allowed)
30    #[serde(default)]
31    pub ip_whitelist: Vec<String>,
32    /// Is the key currently active
33    pub active: bool,
34    /// Usage count
35    #[serde(default)]
36    pub usage_count: u64,
37}
38
39impl ApiKey {
40    /// Create a new API key with secure random generation
41    pub fn new(
42        name: String,
43        role: Role,
44        expires_at: Option<DateTime<Utc>>,
45        ip_whitelist: Vec<String>,
46    ) -> Self {
47        use crate::crypto::hashing::{generate_salt, hash_api_key};
48        use crate::crypto::keys::{generate_key_id, generate_secure_key};
49
50        let role_str = match &role {
51            Role::Admin => "admin",
52            Role::Operator => "op",
53            Role::Monitor => "mon",
54            Role::Device { .. } => "dev",
55            Role::Custom { .. } => "custom",
56        };
57
58        let id = generate_key_id(role_str);
59        let secret = generate_secure_key();
60
61        // Generate salt and hash for secure storage
62        let salt = generate_salt();
63        let secret_hash = hash_api_key(&secret, &salt);
64
65        Self {
66            id,
67            name,
68            key: secret,
69            secret_hash: Some(secret_hash),
70            salt: Some(salt),
71            role,
72            created_at: Utc::now(),
73            expires_at,
74            last_used: None,
75            ip_whitelist,
76            active: true,
77            usage_count: 0,
78        }
79    }
80
81    /// Check if the key is expired
82    pub fn is_expired(&self) -> bool {
83        if let Some(expires_at) = self.expires_at {
84            Utc::now() > expires_at
85        } else {
86            false
87        }
88    }
89
90    /// Check if the key is valid for use
91    pub fn is_valid(&self) -> bool {
92        self.active && !self.is_expired()
93    }
94
95    /// Update last used timestamp
96    pub fn mark_used(&mut self) {
97        self.last_used = Some(Utc::now());
98        self.usage_count += 1;
99    }
100
101    /// Verify if the provided key matches the stored hash
102    pub fn verify_key(
103        &self,
104        provided_key: &str,
105    ) -> Result<bool, crate::crypto::hashing::HashingError> {
106        use crate::crypto::hashing::verify_api_key;
107
108        if let (Some(ref hash), Some(ref salt)) = (&self.secret_hash, &self.salt) {
109            verify_api_key(provided_key, hash, salt)
110        } else {
111            // Fallback to plain text comparison for legacy keys
112            Ok(provided_key == self.key)
113        }
114    }
115
116    /// Convert to secure storage format (without plain text key)
117    pub fn to_secure_storage(&self) -> SecureApiKey {
118        SecureApiKey {
119            id: self.id.clone(),
120            name: self.name.clone(),
121            secret_hash: self.secret_hash.clone(),
122            salt: self.salt.clone(),
123            role: self.role.clone(),
124            created_at: self.created_at,
125            expires_at: self.expires_at,
126            last_used: self.last_used,
127            ip_whitelist: self.ip_whitelist.clone(),
128            active: self.active,
129            usage_count: self.usage_count,
130        }
131    }
132}
133
134/// Secure API key for storage (without plain text key)
135#[derive(Debug, Clone, Serialize, Deserialize)]
136pub struct SecureApiKey {
137    /// Unique key identifier (format: lmcp_{role}_{timestamp}_{random})
138    pub id: String,
139    /// Human-readable name/description
140    pub name: String,
141    /// Secure hash of the secret token (for storage)
142    pub secret_hash: Option<String>,
143    /// Salt used for hashing the secret token
144    pub salt: Option<Salt>,
145    /// Role-based permissions
146    pub role: Role,
147    /// Creation timestamp
148    pub created_at: DateTime<Utc>,
149    /// Optional expiration timestamp
150    pub expires_at: Option<DateTime<Utc>>,
151    /// Last time this key was used
152    pub last_used: Option<DateTime<Utc>>,
153    /// IP address whitelist (empty = all IPs allowed)
154    #[serde(default)]
155    pub ip_whitelist: Vec<String>,
156    /// Is the key currently active
157    pub active: bool,
158    /// Usage count
159    #[serde(default)]
160    pub usage_count: u64,
161}
162
163impl SecureApiKey {
164    /// Convert back to ApiKey (without plain text key)
165    pub fn to_api_key(&self) -> ApiKey {
166        ApiKey {
167            id: self.id.clone(),
168            name: self.name.clone(),
169            key: "***redacted***".to_string(), // Never expose plain text
170            secret_hash: self.secret_hash.clone(),
171            salt: self.salt.clone(),
172            role: self.role.clone(),
173            created_at: self.created_at,
174            expires_at: self.expires_at,
175            last_used: self.last_used,
176            ip_whitelist: self.ip_whitelist.clone(),
177            active: self.active,
178            usage_count: self.usage_count,
179        }
180    }
181
182    /// Check if the key is expired
183    pub fn is_expired(&self) -> bool {
184        if let Some(expires_at) = self.expires_at {
185            Utc::now() > expires_at
186        } else {
187            false
188        }
189    }
190
191    /// Check if the key is valid for use
192    pub fn is_valid(&self) -> bool {
193        self.active && !self.is_expired()
194    }
195
196    /// Verify if the provided key matches the stored hash
197    pub fn verify_key(
198        &self,
199        provided_key: &str,
200    ) -> Result<bool, crate::crypto::hashing::HashingError> {
201        use crate::crypto::hashing::verify_api_key;
202
203        if let (Some(ref hash), Some(ref salt)) = (&self.secret_hash, &self.salt) {
204            verify_api_key(provided_key, hash, salt)
205        } else {
206            // Can't verify without hash - this should not happen in production
207            Ok(false)
208        }
209    }
210}
211
212/// User roles with granular permissions
213#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
214#[serde(rename_all = "lowercase")]
215pub enum Role {
216    /// Full system access - all operations including user management
217    Admin,
218    /// Device control and monitoring - no user/key management
219    Operator,
220    /// Read-only access to all resources and status
221    Monitor,
222    /// Limited access to specific devices only
223    Device {
224        /// List of device UUIDs this key can control
225        allowed_devices: Vec<String>,
226    },
227    /// Custom role with specific permission set
228    Custom {
229        /// List of specific permissions
230        permissions: Vec<String>,
231    },
232}
233
234impl Role {
235    /// Check if this role has a specific permission
236    pub fn has_permission(&self, permission: &str) -> bool {
237        match self {
238            Role::Admin => true,                                 // Admin has all permissions
239            Role::Operator => !permission.starts_with("admin."), // No admin permissions
240            Role::Monitor => permission.starts_with("read.") || permission == "health.check",
241            Role::Device { allowed_devices } => {
242                // Check if permission is for an allowed device
243                if let Some(device_uuid) = permission.strip_prefix("device.") {
244                    allowed_devices.contains(&device_uuid.to_string())
245                } else {
246                    false
247                }
248            }
249            Role::Custom { permissions } => permissions.contains(&permission.to_string()),
250        }
251    }
252
253    /// Get a human-readable description of this role
254    pub fn description(&self) -> String {
255        match self {
256            Role::Admin => "Full administrative access".to_string(),
257            Role::Operator => "Device control and monitoring".to_string(),
258            Role::Monitor => "Read-only system monitoring".to_string(),
259            Role::Device { allowed_devices } => {
260                format!("Device control for {} devices", allowed_devices.len())
261            }
262            Role::Custom { permissions } => {
263                format!("Custom role with {} permissions", permissions.len())
264            }
265        }
266    }
267}
268
269impl fmt::Display for Role {
270    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
271        match self {
272            Role::Admin => write!(f, "admin"),
273            Role::Operator => write!(f, "operator"),
274            Role::Monitor => write!(f, "monitor"),
275            Role::Device { .. } => write!(f, "device"),
276            Role::Custom { .. } => write!(f, "custom"),
277        }
278    }
279}
280
281/// Authentication result
282#[derive(Debug)]
283pub struct AuthResult {
284    pub success: bool,
285    pub user_id: Option<String>,
286    pub roles: Vec<Role>,
287    pub message: Option<String>,
288    /// Rate limiting information
289    pub rate_limited: bool,
290    /// Client IP address
291    pub client_ip: Option<String>,
292}
293
294impl AuthResult {
295    /// Create a successful authentication result
296    pub fn success(user_id: String, roles: Vec<Role>) -> Self {
297        Self {
298            success: true,
299            user_id: Some(user_id),
300            roles,
301            message: None,
302            rate_limited: false,
303            client_ip: None,
304        }
305    }
306
307    /// Create a failed authentication result
308    pub fn failure(message: String) -> Self {
309        Self {
310            success: false,
311            user_id: None,
312            roles: vec![],
313            message: Some(message),
314            rate_limited: false,
315            client_ip: None,
316        }
317    }
318
319    /// Create a rate limited authentication result
320    pub fn rate_limited(client_ip: String) -> Self {
321        Self {
322            success: false,
323            user_id: None,
324            roles: vec![],
325            message: Some("Too many failed attempts".to_string()),
326            rate_limited: true,
327            client_ip: Some(client_ip),
328        }
329    }
330}
331
332/// Authentication context
333#[derive(Debug, Clone, Serialize, Deserialize)]
334pub struct AuthContext {
335    pub user_id: Option<String>,
336    pub roles: Vec<Role>,
337    pub api_key_id: Option<String>,
338    /// Permissions derived from roles
339    pub permissions: Vec<String>,
340}
341
342impl AuthContext {
343    /// Check if this context has a specific permission
344    pub fn has_permission(&self, permission: &str) -> bool {
345        self.roles
346            .iter()
347            .any(|role| role.has_permission(permission))
348    }
349
350    /// Get all permissions for this context
351    pub fn get_all_permissions(&self) -> Vec<String> {
352        self.permissions.clone()
353    }
354}
355
356/// Request for creating an API key
357#[derive(Debug, Clone, Serialize, Deserialize)]
358pub struct KeyCreationRequest {
359    /// Human-readable name for the key
360    pub name: String,
361    /// Role to assign to the key
362    pub role: Role,
363    /// Optional expiration date
364    pub expires_at: Option<DateTime<Utc>>,
365    /// Optional IP whitelist
366    pub ip_whitelist: Option<Vec<String>>,
367}
368
369/// API key usage statistics
370#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
371pub struct KeyUsageStats {
372    /// Total number of keys
373    pub total_keys: u32,
374    /// Number of active keys
375    pub active_keys: u32,
376    /// Number of disabled keys
377    pub disabled_keys: u32,
378    /// Number of expired keys
379    pub expired_keys: u32,
380    /// Total usage count across all keys
381    pub total_usage_count: u64,
382    /// Admin role keys
383    pub admin_keys: u32,
384    /// Operator role keys
385    pub operator_keys: u32,
386    /// Monitor role keys
387    pub monitor_keys: u32,
388    /// Device role keys
389    pub device_keys: u32,
390    /// Custom role keys
391    pub custom_keys: u32,
392}
393
394/// API completeness check result
395#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
396pub struct ApiCompletenessCheck {
397    /// Has create_key method
398    pub has_create_key: bool,
399    /// Has validate_key method
400    pub has_validate_key: bool,
401    /// Has list_keys method
402    pub has_list_keys: bool,
403    /// Has revoke_key method
404    pub has_revoke_key: bool,
405    /// Has update_key method
406    pub has_update_key: bool,
407    /// Has bulk operations
408    pub has_bulk_operations: bool,
409    /// Has role-based access control
410    pub has_role_based_access: bool,
411    /// Has rate limiting
412    pub has_rate_limiting: bool,
413    /// Has IP whitelisting
414    pub has_ip_whitelisting: bool,
415    /// Has expiration support
416    pub has_expiration_support: bool,
417    /// Has usage tracking
418    pub has_usage_tracking: bool,
419    /// Framework version
420    pub framework_version: String,
421    /// Is production ready
422    pub production_ready: bool,
423}