Skip to main content

uvb_storage_memory/
secret.rs

1use async_trait::async_trait;
2use std::collections::HashMap;
3use std::sync::Arc;
4use tokio::sync::RwLock;
5use uvb_core::TenantId;
6use uvb_storage_api::{SecretError, SecretRecord, SecretStore};
7
8/// Type alias for secret index: (user_id, tenant_id, factor_id) -> secret_id
9type SecretIndex = HashMap<(String, TenantId, String), String>;
10
11/// In-memory secret store (NOT for production - secrets should be encrypted at rest)
12pub struct InMemorySecretStore {
13    secrets: Arc<RwLock<HashMap<String, SecretRecord>>>,
14    // Index: (user_id, tenant_id, factor_id) -> secret_id
15    index: Arc<RwLock<SecretIndex>>,
16}
17
18impl InMemorySecretStore {
19    pub fn new() -> Self {
20        Self {
21            secrets: Arc::new(RwLock::new(HashMap::new())),
22            index: Arc::new(RwLock::new(HashMap::new())),
23        }
24    }
25}
26
27impl Default for InMemorySecretStore {
28    fn default() -> Self {
29        Self::new()
30    }
31}
32
33#[async_trait]
34impl SecretStore for InMemorySecretStore {
35    async fn set(
36        &self,
37        user_id: &str,
38        tenant_id: &TenantId,
39        factor_id: &str,
40        secret_data: &[u8],
41        metadata: serde_json::Value,
42    ) -> Result<String, SecretError> {
43        let id = uuid::Uuid::new_v4().to_string();
44        let now = std::time::SystemTime::now();
45
46        let record = SecretRecord {
47            id: id.clone(),
48            user_id: user_id.to_string(),
49            tenant_id: tenant_id.clone(),
50            factor_id: factor_id.to_string(),
51            secret_data: secret_data.to_vec(),
52            metadata,
53            created_at: now,
54            updated_at: now,
55        };
56
57        let key = (
58            user_id.to_string(),
59            tenant_id.clone(),
60            factor_id.to_string(),
61        );
62
63        self.secrets.write().await.insert(id.clone(), record);
64        self.index.write().await.insert(key.clone(), id.clone());
65
66        tracing::info!(
67            secret_id = %id,
68            user_id = %user_id,
69            tenant_id = %tenant_id,
70            factor_id = %factor_id,
71            "Stored secret in memory"
72        );
73
74        Ok(id)
75    }
76
77    async fn get(
78        &self,
79        user_id: &str,
80        tenant_id: &TenantId,
81        factor_id: &str,
82    ) -> Result<Option<SecretRecord>, SecretError> {
83        let key = (
84            user_id.to_string(),
85            tenant_id.clone(),
86            factor_id.to_string(),
87        );
88
89        let index = self.index.read().await;
90        let total_secrets = self.secrets.read().await.len();
91        let total_index_entries = index.len();
92
93        if let Some(id) = index.get(&key) {
94            let secrets = self.secrets.read().await;
95            let found = secrets.get(id).is_some();
96            tracing::info!(
97                user_id = %user_id,
98                tenant_id = %tenant_id,
99                factor_id = %factor_id,
100                secret_id = %id,
101                found = found,
102                total_secrets = total_secrets,
103                total_index_entries = total_index_entries,
104                "Retrieved secret from memory"
105            );
106            Ok(secrets.get(id).cloned())
107        } else {
108            tracing::warn!(
109                user_id = %user_id,
110                tenant_id = %tenant_id,
111                factor_id = %factor_id,
112                total_secrets = total_secrets,
113                total_index_entries = total_index_entries,
114                "Secret not found in memory index"
115            );
116            Ok(None)
117        }
118    }
119
120    async fn get_by_id(&self, id: &str) -> Result<Option<SecretRecord>, SecretError> {
121        let secrets = self.secrets.read().await;
122        Ok(secrets.get(id).cloned())
123    }
124
125    async fn delete(&self, id: &str) -> Result<(), SecretError> {
126        let mut secrets = self.secrets.write().await;
127        if let Some(record) = secrets.remove(id) {
128            let key = (record.user_id, record.tenant_id, record.factor_id);
129            self.index.write().await.remove(&key);
130            Ok(())
131        } else {
132            Err(SecretError::NotFound)
133        }
134    }
135
136    async fn list(
137        &self,
138        user_id: &str,
139        tenant_id: &TenantId,
140        factor_id: Option<&str>,
141    ) -> Result<Vec<SecretRecord>, SecretError> {
142        let secrets = self.secrets.read().await;
143        let results = secrets
144            .values()
145            .filter(|s| {
146                s.user_id == user_id
147                    && &s.tenant_id == tenant_id
148                    && (factor_id.is_none() || factor_id == Some(s.factor_id.as_str()))
149            })
150            .cloned()
151            .collect();
152
153        Ok(results)
154    }
155}