use async_trait::async_trait;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
use uvb_core::TenantId;
use uvb_storage_api::{SecretError, SecretRecord, SecretStore};
type SecretIndex = HashMap<(String, TenantId, String), String>;
pub struct InMemorySecretStore {
secrets: Arc<RwLock<HashMap<String, SecretRecord>>>,
index: Arc<RwLock<SecretIndex>>,
}
impl InMemorySecretStore {
pub fn new() -> Self {
Self {
secrets: Arc::new(RwLock::new(HashMap::new())),
index: Arc::new(RwLock::new(HashMap::new())),
}
}
}
impl Default for InMemorySecretStore {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
impl SecretStore for InMemorySecretStore {
async fn set(
&self,
user_id: &str,
tenant_id: &TenantId,
factor_id: &str,
secret_data: &[u8],
metadata: serde_json::Value,
) -> Result<String, SecretError> {
let id = uuid::Uuid::new_v4().to_string();
let now = std::time::SystemTime::now();
let record = SecretRecord {
id: id.clone(),
user_id: user_id.to_string(),
tenant_id: tenant_id.clone(),
factor_id: factor_id.to_string(),
secret_data: secret_data.to_vec(),
metadata,
created_at: now,
updated_at: now,
};
let key = (
user_id.to_string(),
tenant_id.clone(),
factor_id.to_string(),
);
self.secrets.write().await.insert(id.clone(), record);
self.index.write().await.insert(key.clone(), id.clone());
tracing::info!(
secret_id = %id,
user_id = %user_id,
tenant_id = %tenant_id,
factor_id = %factor_id,
"Stored secret in memory"
);
Ok(id)
}
async fn get(
&self,
user_id: &str,
tenant_id: &TenantId,
factor_id: &str,
) -> Result<Option<SecretRecord>, SecretError> {
let key = (
user_id.to_string(),
tenant_id.clone(),
factor_id.to_string(),
);
let index = self.index.read().await;
let total_secrets = self.secrets.read().await.len();
let total_index_entries = index.len();
if let Some(id) = index.get(&key) {
let secrets = self.secrets.read().await;
let found = secrets.get(id).is_some();
tracing::info!(
user_id = %user_id,
tenant_id = %tenant_id,
factor_id = %factor_id,
secret_id = %id,
found = found,
total_secrets = total_secrets,
total_index_entries = total_index_entries,
"Retrieved secret from memory"
);
Ok(secrets.get(id).cloned())
} else {
tracing::warn!(
user_id = %user_id,
tenant_id = %tenant_id,
factor_id = %factor_id,
total_secrets = total_secrets,
total_index_entries = total_index_entries,
"Secret not found in memory index"
);
Ok(None)
}
}
async fn get_by_id(&self, id: &str) -> Result<Option<SecretRecord>, SecretError> {
let secrets = self.secrets.read().await;
Ok(secrets.get(id).cloned())
}
async fn delete(&self, id: &str) -> Result<(), SecretError> {
let mut secrets = self.secrets.write().await;
if let Some(record) = secrets.remove(id) {
let key = (record.user_id, record.tenant_id, record.factor_id);
self.index.write().await.remove(&key);
Ok(())
} else {
Err(SecretError::NotFound)
}
}
async fn list(
&self,
user_id: &str,
tenant_id: &TenantId,
factor_id: Option<&str>,
) -> Result<Vec<SecretRecord>, SecretError> {
let secrets = self.secrets.read().await;
let results = secrets
.values()
.filter(|s| {
s.user_id == user_id
&& &s.tenant_id == tenant_id
&& (factor_id.is_none() || factor_id == Some(s.factor_id.as_str()))
})
.cloned()
.collect();
Ok(results)
}
}