Skip to main content

qudag_mcp/
auth.rs

1//! Authentication and authorization for QuDAG MCP.
2
3use crate::error::{Error, Result};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::time::{SystemTime, UNIX_EPOCH};
7
8/// Authentication manager
9#[derive(Debug, Clone)]
10pub struct AuthManager {
11    /// Authentication configuration
12    config: AuthConfig,
13    /// Active sessions
14    sessions: HashMap<String, AuthSession>,
15}
16
17/// Authentication configuration
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct AuthConfig {
20    /// Whether authentication is required
21    pub required: bool,
22    /// Supported authentication methods
23    pub methods: Vec<AuthMethod>,
24    /// Session timeout in seconds
25    pub session_timeout: u64,
26    /// Maximum concurrent sessions
27    pub max_sessions: usize,
28}
29
30/// Authentication method
31#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
32pub enum AuthMethod {
33    /// API key authentication
34    #[serde(rename = "api_key")]
35    ApiKey,
36    /// OAuth2 authentication
37    #[serde(rename = "oauth2")]
38    OAuth2,
39    /// Vault token authentication
40    #[serde(rename = "vault_token")]
41    VaultToken,
42    /// No authentication
43    #[serde(rename = "none")]
44    None,
45}
46
47/// Authentication session
48#[derive(Debug, Clone)]
49pub struct AuthSession {
50    /// Session ID
51    pub id: String,
52    /// User ID
53    pub user_id: String,
54    /// Authentication method used
55    pub method: AuthMethod,
56    /// Session creation time
57    pub created_at: SystemTime,
58    /// Session last activity
59    pub last_activity: SystemTime,
60    /// Session permissions
61    pub permissions: Vec<Permission>,
62}
63
64/// Permission type
65#[derive(Debug, Clone, PartialEq, Eq)]
66pub enum Permission {
67    /// Read DAG data
68    DagRead,
69    /// Write DAG data
70    DagWrite,
71    /// Read vault data
72    VaultRead,
73    /// Write vault data
74    VaultWrite,
75    /// Network operations
76    NetworkAccess,
77    /// Crypto operations
78    CryptoAccess,
79    /// Admin operations
80    Admin,
81}
82
83/// Authentication request
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct AuthRequest {
86    /// Authentication method
87    pub method: AuthMethod,
88    /// Credentials
89    pub credentials: HashMap<String, String>,
90    /// Client information
91    pub client_info: Option<crate::types::ClientInfo>,
92}
93
94/// Authentication response
95#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct AuthResponse {
97    /// Whether authentication was successful
98    pub success: bool,
99    /// Session token (if successful)
100    pub session_token: Option<String>,
101    /// Error message (if failed)
102    pub error: Option<String>,
103    /// User permissions
104    pub permissions: Option<Vec<String>>,
105}
106
107impl AuthManager {
108    /// Create new authentication manager
109    pub fn new(config: AuthConfig) -> Self {
110        Self {
111            config,
112            sessions: HashMap::new(),
113        }
114    }
115
116    /// Authenticate a request
117    pub async fn authenticate(&mut self, request: AuthRequest) -> Result<AuthResponse> {
118        if !self.config.required || request.method == AuthMethod::None {
119            // No authentication required or explicitly disabled
120            return Ok(AuthResponse {
121                success: true,
122                session_token: None,
123                error: None,
124                permissions: Some(vec!["read".to_string(), "write".to_string()]),
125            });
126        }
127
128        match request.method {
129            AuthMethod::ApiKey => self.authenticate_api_key(&request.credentials).await,
130            AuthMethod::OAuth2 => self.authenticate_oauth2(&request.credentials).await,
131            AuthMethod::VaultToken => self.authenticate_vault_token(&request.credentials).await,
132            AuthMethod::None => Ok(AuthResponse {
133                success: true,
134                session_token: None,
135                error: None,
136                permissions: Some(vec!["read".to_string()]),
137            }),
138        }
139    }
140
141    /// Validate a session token
142    pub async fn validate_session(&mut self, token: &str) -> Result<Option<&AuthSession>> {
143        if let Some(session) = self.sessions.get(token) {
144            // Check if session is expired
145            let now = SystemTime::now();
146            let age = now.duration_since(session.created_at)?;
147
148            if age.as_secs() > self.config.session_timeout {
149                // Session expired, remove it
150                self.sessions.remove(token);
151                return Ok(None);
152            }
153
154            // Update last activity
155            if let Some(session) = self.sessions.get_mut(token) {
156                session.last_activity = now;
157            }
158
159            Ok(self.sessions.get(token))
160        } else {
161            Ok(None)
162        }
163    }
164
165    /// Check if user has permission
166    pub fn has_permission(&self, session_token: &str, permission: Permission) -> bool {
167        if let Some(session) = self.sessions.get(session_token) {
168            session.permissions.contains(&permission)
169        } else {
170            false
171        }
172    }
173
174    /// Create a new session
175    fn create_session(
176        &mut self,
177        user_id: String,
178        method: AuthMethod,
179        permissions: Vec<Permission>,
180    ) -> Result<String> {
181        // Check max sessions
182        if self.sessions.len() >= self.config.max_sessions {
183            return Err(Error::auth("Maximum concurrent sessions reached"));
184        }
185
186        let session_id = uuid::Uuid::new_v4().to_string();
187        let now = SystemTime::now();
188
189        let session = AuthSession {
190            id: session_id.clone(),
191            user_id,
192            method,
193            created_at: now,
194            last_activity: now,
195            permissions,
196        };
197
198        self.sessions.insert(session_id.clone(), session);
199        Ok(session_id)
200    }
201
202    /// Authenticate with API key
203    async fn authenticate_api_key(
204        &mut self,
205        credentials: &HashMap<String, String>,
206    ) -> Result<AuthResponse> {
207        let api_key = credentials
208            .get("api_key")
209            .ok_or_else(|| Error::auth("API key not provided"))?;
210
211        // In a real implementation, you would validate the API key against a database
212        // For now, we'll accept any non-empty key
213        if api_key.is_empty() {
214            return Ok(AuthResponse {
215                success: false,
216                session_token: None,
217                error: Some("Invalid API key".to_string()),
218                permissions: None,
219            });
220        }
221
222        let user_id = format!(
223            "api_key_user_{}",
224            api_key.chars().take(8).collect::<String>()
225        );
226        let permissions = vec![
227            Permission::DagRead,
228            Permission::DagWrite,
229            Permission::VaultRead,
230            Permission::NetworkAccess,
231            Permission::CryptoAccess,
232        ];
233
234        let session_token = self.create_session(user_id, AuthMethod::ApiKey, permissions)?;
235
236        Ok(AuthResponse {
237            success: true,
238            session_token: Some(session_token),
239            error: None,
240            permissions: Some(vec![
241                "dag_read".to_string(),
242                "dag_write".to_string(),
243                "vault_read".to_string(),
244                "network_access".to_string(),
245                "crypto_access".to_string(),
246            ]),
247        })
248    }
249
250    /// Authenticate with OAuth2
251    async fn authenticate_oauth2(
252        &mut self,
253        credentials: &HashMap<String, String>,
254    ) -> Result<AuthResponse> {
255        let access_token = credentials
256            .get("access_token")
257            .ok_or_else(|| Error::auth("Access token not provided"))?;
258
259        // In a real implementation, you would validate the token with the OAuth2 provider
260        // For now, we'll accept any non-empty token
261        if access_token.is_empty() {
262            return Ok(AuthResponse {
263                success: false,
264                session_token: None,
265                error: Some("Invalid access token".to_string()),
266                permissions: None,
267            });
268        }
269
270        let user_id = format!(
271            "oauth2_user_{}",
272            access_token.chars().take(8).collect::<String>()
273        );
274        let permissions = vec![
275            Permission::DagRead,
276            Permission::VaultRead,
277            Permission::NetworkAccess,
278            Permission::CryptoAccess,
279        ];
280
281        let session_token = self.create_session(user_id, AuthMethod::OAuth2, permissions)?;
282
283        Ok(AuthResponse {
284            success: true,
285            session_token: Some(session_token),
286            error: None,
287            permissions: Some(vec![
288                "dag_read".to_string(),
289                "vault_read".to_string(),
290                "network_access".to_string(),
291                "crypto_access".to_string(),
292            ]),
293        })
294    }
295
296    /// Authenticate with vault token
297    async fn authenticate_vault_token(
298        &mut self,
299        credentials: &HashMap<String, String>,
300    ) -> Result<AuthResponse> {
301        let vault_token = credentials
302            .get("vault_token")
303            .ok_or_else(|| Error::auth("Vault token not provided"))?;
304
305        // In a real implementation, you would validate the token with the vault
306        // For now, we'll accept any non-empty token
307        if vault_token.is_empty() {
308            return Ok(AuthResponse {
309                success: false,
310                session_token: None,
311                error: Some("Invalid vault token".to_string()),
312                permissions: None,
313            });
314        }
315
316        let user_id = format!(
317            "vault_user_{}",
318            vault_token.chars().take(8).collect::<String>()
319        );
320        let permissions = vec![
321            Permission::DagRead,
322            Permission::DagWrite,
323            Permission::VaultRead,
324            Permission::VaultWrite,
325            Permission::NetworkAccess,
326            Permission::CryptoAccess,
327            Permission::Admin,
328        ];
329
330        let session_token = self.create_session(user_id, AuthMethod::VaultToken, permissions)?;
331
332        Ok(AuthResponse {
333            success: true,
334            session_token: Some(session_token),
335            error: None,
336            permissions: Some(vec![
337                "dag_read".to_string(),
338                "dag_write".to_string(),
339                "vault_read".to_string(),
340                "vault_write".to_string(),
341                "network_access".to_string(),
342                "crypto_access".to_string(),
343                "admin".to_string(),
344            ]),
345        })
346    }
347
348    /// Clean up expired sessions
349    pub fn cleanup_expired_sessions(&mut self) {
350        let now = SystemTime::now();
351        let timeout = std::time::Duration::from_secs(self.config.session_timeout);
352
353        self.sessions.retain(|_, session| {
354            now.duration_since(session.created_at)
355                .map(|age| age < timeout)
356                .unwrap_or(false)
357        });
358    }
359}
360
361impl Default for AuthConfig {
362    fn default() -> Self {
363        Self {
364            required: false,
365            methods: vec![AuthMethod::None],
366            session_timeout: 3600, // 1 hour
367            max_sessions: 100,
368        }
369    }
370}
371
372impl std::fmt::Display for Permission {
373    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
374        match self {
375            Permission::DagRead => write!(f, "dag_read"),
376            Permission::DagWrite => write!(f, "dag_write"),
377            Permission::VaultRead => write!(f, "vault_read"),
378            Permission::VaultWrite => write!(f, "vault_write"),
379            Permission::NetworkAccess => write!(f, "network_access"),
380            Permission::CryptoAccess => write!(f, "crypto_access"),
381            Permission::Admin => write!(f, "admin"),
382        }
383    }
384}
385
386#[cfg(test)]
387mod tests {
388    use super::*;
389
390    #[tokio::test]
391    async fn test_auth_disabled() {
392        let config = AuthConfig {
393            required: false,
394            ..Default::default()
395        };
396        let mut auth = AuthManager::new(config);
397
398        let request = AuthRequest {
399            method: AuthMethod::None,
400            credentials: HashMap::new(),
401            client_info: None,
402        };
403
404        let response = auth.authenticate(request).await.unwrap();
405        assert!(response.success);
406    }
407
408    #[tokio::test]
409    async fn test_api_key_auth() {
410        let config = AuthConfig {
411            required: true,
412            methods: vec![AuthMethod::ApiKey],
413            ..Default::default()
414        };
415        let mut auth = AuthManager::new(config);
416
417        let mut credentials = HashMap::new();
418        credentials.insert("api_key".to_string(), "test_key_123".to_string());
419
420        let request = AuthRequest {
421            method: AuthMethod::ApiKey,
422            credentials,
423            client_info: None,
424        };
425
426        let response = auth.authenticate(request).await.unwrap();
427        assert!(response.success);
428        assert!(response.session_token.is_some());
429    }
430
431    #[tokio::test]
432    async fn test_session_validation() {
433        let config = AuthConfig {
434            required: true,
435            methods: vec![AuthMethod::ApiKey],
436            session_timeout: 60, // 1 minute
437            ..Default::default()
438        };
439        let mut auth = AuthManager::new(config);
440
441        let mut credentials = HashMap::new();
442        credentials.insert("api_key".to_string(), "test_key_123".to_string());
443
444        let request = AuthRequest {
445            method: AuthMethod::ApiKey,
446            credentials,
447            client_info: None,
448        };
449
450        let response = auth.authenticate(request).await.unwrap();
451        let token = response.session_token.unwrap();
452
453        // Validate the session
454        let session = auth.validate_session(&token).await.unwrap();
455        assert!(session.is_some());
456
457        // Validate with invalid token
458        let invalid_session = auth.validate_session("invalid_token").await.unwrap();
459        assert!(invalid_session.is_none());
460    }
461}