1use crate::error::{Error, Result};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::time::{SystemTime, UNIX_EPOCH};
7
8#[derive(Debug, Clone)]
10pub struct AuthManager {
11 config: AuthConfig,
13 sessions: HashMap<String, AuthSession>,
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct AuthConfig {
20 pub required: bool,
22 pub methods: Vec<AuthMethod>,
24 pub session_timeout: u64,
26 pub max_sessions: usize,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
32pub enum AuthMethod {
33 #[serde(rename = "api_key")]
35 ApiKey,
36 #[serde(rename = "oauth2")]
38 OAuth2,
39 #[serde(rename = "vault_token")]
41 VaultToken,
42 #[serde(rename = "none")]
44 None,
45}
46
47#[derive(Debug, Clone)]
49pub struct AuthSession {
50 pub id: String,
52 pub user_id: String,
54 pub method: AuthMethod,
56 pub created_at: SystemTime,
58 pub last_activity: SystemTime,
60 pub permissions: Vec<Permission>,
62}
63
64#[derive(Debug, Clone, PartialEq, Eq)]
66pub enum Permission {
67 DagRead,
69 DagWrite,
71 VaultRead,
73 VaultWrite,
75 NetworkAccess,
77 CryptoAccess,
79 Admin,
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct AuthRequest {
86 pub method: AuthMethod,
88 pub credentials: HashMap<String, String>,
90 pub client_info: Option<crate::types::ClientInfo>,
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct AuthResponse {
97 pub success: bool,
99 pub session_token: Option<String>,
101 pub error: Option<String>,
103 pub permissions: Option<Vec<String>>,
105}
106
107impl AuthManager {
108 pub fn new(config: AuthConfig) -> Self {
110 Self {
111 config,
112 sessions: HashMap::new(),
113 }
114 }
115
116 pub async fn authenticate(&mut self, request: AuthRequest) -> Result<AuthResponse> {
118 if !self.config.required || request.method == AuthMethod::None {
119 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 pub async fn validate_session(&mut self, token: &str) -> Result<Option<&AuthSession>> {
143 if let Some(session) = self.sessions.get(token) {
144 let now = SystemTime::now();
146 let age = now.duration_since(session.created_at)?;
147
148 if age.as_secs() > self.config.session_timeout {
149 self.sessions.remove(token);
151 return Ok(None);
152 }
153
154 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 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 fn create_session(
176 &mut self,
177 user_id: String,
178 method: AuthMethod,
179 permissions: Vec<Permission>,
180 ) -> Result<String> {
181 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 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 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 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 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 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 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 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, 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, ..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 let session = auth.validate_session(&token).await.unwrap();
455 assert!(session.is_some());
456
457 let invalid_session = auth.validate_session("invalid_token").await.unwrap();
459 assert!(invalid_session.is_none());
460 }
461}