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