mockforge_core/security/
api_tokens.rs1use 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#[async_trait::async_trait]
18pub trait ApiTokenStorage: Send + Sync {
19 async fn get_all_tokens(&self) -> Result<Vec<ApiTokenInfo>, Error>;
21
22 async fn get_token(&self, token_id: &str) -> Result<Option<ApiTokenInfo>, Error>;
24
25 async fn create_token(&self, token: ApiTokenInfo) -> Result<(), Error>;
27
28 async fn update_token(&self, token_id: &str, token: ApiTokenInfo) -> Result<(), Error>;
30
31 async fn revoke_token(&self, token_id: &str) -> Result<(), Error>;
33
34 async fn get_tokens_by_owner(&self, owner_id: Uuid) -> Result<Vec<ApiTokenInfo>, Error>;
36}
37
38pub struct InMemoryApiTokenStorage {
40 tokens: Arc<RwLock<HashMap<String, ApiTokenInfo>>>,
41}
42
43impl InMemoryApiTokenStorage {
44 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.values().filter(|t| t.owner_id == owner_id).cloned().collect())
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 #[tokio::test]
102 async fn test_in_memory_storage() {
103 let storage = InMemoryApiTokenStorage::new();
104 let token = ApiTokenInfo {
105 token_id: "test-token".to_string(),
106 name: Some("Test Token".to_string()),
107 owner_id: Uuid::new_v4(),
108 scopes: vec!["read".to_string(), "write".to_string()],
109 created_at: Utc::now(),
110 last_used: None,
111 expires_at: Some(Utc::now() + chrono::Duration::days(30)),
112 days_unused: None,
113 is_active: true,
114 };
115
116 storage.create_token(token.clone()).await.unwrap();
117 let retrieved = storage.get_token("test-token").await.unwrap();
118 assert!(retrieved.is_some());
119 assert_eq!(retrieved.unwrap().token_id, "test-token");
120 }
121}