mockforge_core/security/
api_tokens.rs1use 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#[async_trait::async_trait]
17pub trait ApiTokenStorage: Send + Sync {
18 async fn get_all_tokens(&self) -> Result<Vec<ApiTokenInfo>, Error>;
20
21 async fn get_token(&self, token_id: &str) -> Result<Option<ApiTokenInfo>, Error>;
23
24 async fn create_token(&self, token: ApiTokenInfo) -> Result<(), Error>;
26
27 async fn update_token(&self, token_id: &str, token: ApiTokenInfo) -> Result<(), Error>;
29
30 async fn revoke_token(&self, token_id: &str) -> Result<(), Error>;
32
33 async fn get_tokens_by_owner(&self, owner_id: Uuid) -> Result<Vec<ApiTokenInfo>, Error>;
35}
36
37pub struct InMemoryApiTokenStorage {
39 tokens: Arc<RwLock<HashMap<String, ApiTokenInfo>>>,
40}
41
42impl InMemoryApiTokenStorage {
43 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}