mockforge_core/security/
api_tokens.rs

1//! API token storage and management for access reviews
2//!
3//! This module provides storage and retrieval of API tokens for review purposes.
4
5use crate::security::access_review::ApiTokenInfo;
6use crate::Error;
7use chrono::{DateTime, Utc};
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use std::sync::Arc;
11use tokio::sync::RwLock;
12use uuid::Uuid;
13
14/// API token storage trait
15///
16/// This allows different storage backends (database, in-memory, etc.)
17#[async_trait::async_trait]
18pub trait ApiTokenStorage: Send + Sync {
19    /// Get all API tokens
20    async fn get_all_tokens(&self) -> Result<Vec<ApiTokenInfo>, Error>;
21
22    /// Get token by ID
23    async fn get_token(&self, token_id: &str) -> Result<Option<ApiTokenInfo>, Error>;
24
25    /// Create a new token
26    async fn create_token(&self, token: ApiTokenInfo) -> Result<(), Error>;
27
28    /// Update token (e.g., last_used timestamp)
29    async fn update_token(&self, token_id: &str, token: ApiTokenInfo) -> Result<(), Error>;
30
31    /// Delete/revoke a token
32    async fn revoke_token(&self, token_id: &str) -> Result<(), Error>;
33
34    /// Get tokens by owner
35    async fn get_tokens_by_owner(&self, owner_id: Uuid) -> Result<Vec<ApiTokenInfo>, Error>;
36}
37
38/// In-memory API token storage (for development/testing)
39pub struct InMemoryApiTokenStorage {
40    tokens: Arc<RwLock<HashMap<String, ApiTokenInfo>>>,
41}
42
43impl InMemoryApiTokenStorage {
44    /// Create a new in-memory token storage
45    pub fn new() -> Self {
46        Self {
47            tokens: Arc::new(RwLock::new(HashMap::new())),
48        }
49    }
50}
51
52impl Default for InMemoryApiTokenStorage {
53    fn default() -> Self {
54        Self::new()
55    }
56}
57
58#[async_trait::async_trait]
59impl ApiTokenStorage for InMemoryApiTokenStorage {
60    async fn get_all_tokens(&self) -> Result<Vec<ApiTokenInfo>, Error> {
61        let tokens = self.tokens.read().await;
62        Ok(tokens.values().cloned().collect())
63    }
64
65    async fn get_token(&self, token_id: &str) -> Result<Option<ApiTokenInfo>, Error> {
66        let tokens = self.tokens.read().await;
67        Ok(tokens.get(token_id).cloned())
68    }
69
70    async fn create_token(&self, token: ApiTokenInfo) -> Result<(), Error> {
71        let mut tokens = self.tokens.write().await;
72        tokens.insert(token.token_id.clone(), token);
73        Ok(())
74    }
75
76    async fn update_token(&self, token_id: &str, token: ApiTokenInfo) -> Result<(), Error> {
77        let mut tokens = self.tokens.write().await;
78        tokens.insert(token_id.to_string(), token);
79        Ok(())
80    }
81
82    async fn revoke_token(&self, token_id: &str) -> Result<(), Error> {
83        let mut tokens = self.tokens.write().await;
84        if let Some(mut token) = tokens.remove(token_id) {
85            token.is_active = false;
86            tokens.insert(token_id.to_string(), token);
87        }
88        Ok(())
89    }
90
91    async fn get_tokens_by_owner(&self, owner_id: Uuid) -> Result<Vec<ApiTokenInfo>, Error> {
92        let tokens = self.tokens.read().await;
93        Ok(tokens
94            .values()
95            .filter(|t| t.owner_id == owner_id)
96            .cloned()
97            .collect())
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104
105    #[tokio::test]
106    async fn test_in_memory_storage() {
107        let storage = InMemoryApiTokenStorage::new();
108        let token = ApiTokenInfo {
109            token_id: "test-token".to_string(),
110            name: Some("Test Token".to_string()),
111            owner_id: Uuid::new_v4(),
112            scopes: vec!["read".to_string(), "write".to_string()],
113            created_at: Utc::now(),
114            last_used: None,
115            expires_at: Some(Utc::now() + chrono::Duration::days(30)),
116            days_unused: None,
117            is_active: true,
118        };
119
120        storage.create_token(token.clone()).await.unwrap();
121        let retrieved = storage.get_token("test-token").await.unwrap();
122        assert!(retrieved.is_some());
123        assert_eq!(retrieved.unwrap().token_id, "test-token");
124    }
125}