Skip to main content

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