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