use crate::utils::error::{Error, Result};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::marker::PhantomData;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use tokio::io::AsyncWriteExt;
use tokio::sync::RwLock;
#[derive(Clone)]
pub struct Plaintext;
#[derive(Clone)]
pub struct Encrypted;
#[derive(Clone)]
pub struct Ready;
#[derive(Clone)]
pub struct Rotating;
#[derive(Clone)]
pub struct Secret<State> {
id: String,
value: SecretValue,
metadata: SecretMetadata,
_state: PhantomData<State>,
}
impl<State> Secret<State> {
#[must_use]
pub fn id(&self) -> &str {
&self.id
}
#[must_use]
pub fn metadata(&self) -> &SecretMetadata {
&self.metadata
}
}
impl Secret<Plaintext> {
fn new(id: String, value: String, secret_type: SecretType) -> Self {
Self {
id: id.clone(),
value: SecretValue::Plaintext(value),
metadata: SecretMetadata {
secret_type,
version: 1,
created_at: Utc::now(),
last_rotated: None,
last_accessed: None,
},
_state: PhantomData,
}
}
fn encrypt(self, encryption_provider: &EncryptionProvider) -> Result<Secret<Encrypted>> {
let encrypted_value = encryption_provider.encrypt(&self.value.as_plaintext())?;
Ok(Secret {
id: self.id,
value: SecretValue::Encrypted(encrypted_value),
metadata: self.metadata,
_state: PhantomData,
})
}
#[allow(dead_code)] fn begin_rotation(self) -> Secret<Rotating> {
Secret {
id: self.id,
value: self.value,
metadata: self.metadata,
_state: PhantomData,
}
}
}
impl Secret<Encrypted> {
fn decrypt(self, encryption_provider: &EncryptionProvider) -> Result<Secret<Ready>> {
let plaintext = encryption_provider.decrypt(&self.value.as_encrypted())?;
Ok(Secret {
id: self.id,
value: SecretValue::Plaintext(plaintext),
metadata: self.metadata,
_state: PhantomData,
})
}
}
impl Secret<Ready> {
#[must_use]
pub fn value(&self) -> &str {
self.value.as_plaintext()
}
fn begin_rotation(self) -> Secret<Rotating> {
Secret {
id: self.id,
value: self.value,
metadata: self.metadata,
_state: PhantomData,
}
}
}
impl Secret<Rotating> {
fn complete_rotation(mut self, new_value: String) -> Secret<Plaintext> {
self.value = SecretValue::Plaintext(new_value);
self.metadata.version += 1;
self.metadata.last_rotated = Some(Utc::now());
Secret {
id: self.id,
value: self.value,
metadata: self.metadata,
_state: PhantomData,
}
}
}
#[derive(Clone, Serialize, Deserialize)]
enum SecretValue {
Plaintext(String),
Encrypted(Vec<u8>),
}
impl SecretValue {
fn as_plaintext(&self) -> &str {
match self {
Self::Plaintext(s) => s,
Self::Encrypted(_) => panic!("Cannot access encrypted secret as plaintext"),
}
}
fn as_encrypted(&self) -> &[u8] {
match self {
Self::Encrypted(data) => data,
Self::Plaintext(_) => panic!("Cannot access plaintext secret as encrypted"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecretMetadata {
pub secret_type: SecretType,
pub version: u64,
pub created_at: DateTime<Utc>,
pub last_rotated: Option<DateTime<Utc>>,
pub last_accessed: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum SecretType {
ApiKey,
DatabaseCredential,
TlsCertificate,
SigningKey,
Generic,
}
#[derive(Clone, Debug)]
pub struct EncryptionProvider {
key: [u8; 32],
}
impl EncryptionProvider {
pub fn new(key: &[u8]) -> Result<Self> {
if key.len() != 32 {
return Err(Error::new("Encryption key must be exactly 32 bytes"));
}
let mut key_array = [0u8; 32];
key_array.copy_from_slice(key);
Ok(Self { key: key_array })
}
pub fn encrypt(&self, plaintext: &str) -> Result<Vec<u8>> {
use aes_gcm::{
aead::{Aead, AeadCore, KeyInit, OsRng},
Aes256Gcm,
};
let cipher = Aes256Gcm::new_from_slice(&self.key)
.map_err(|e| Error::new(&format!("Failed to create cipher: {}", e)))?;
let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
let ciphertext = cipher
.encrypt(&nonce, plaintext.as_bytes())
.map_err(|e| Error::new(&format!("Encryption failed: {}", e)))?;
let mut result = nonce.to_vec();
result.extend_from_slice(&ciphertext);
Ok(result)
}
pub fn decrypt(&self, encrypted: &[u8]) -> Result<String> {
use aes_gcm::{aead::Aead, Aes256Gcm, KeyInit, Nonce};
if encrypted.len() < 12 {
return Err(Error::new("Encrypted data too short"));
}
let cipher = Aes256Gcm::new_from_slice(&self.key)
.map_err(|e| Error::new(&format!("Failed to create cipher: {}", e)))?;
let nonce = Nonce::from_slice(&encrypted[0..12]);
let ciphertext = &encrypted[12..];
let plaintext_bytes = cipher
.decrypt(nonce, ciphertext)
.map_err(|e| Error::new(&format!("Decryption failed: {}", e)))?;
String::from_utf8(plaintext_bytes)
.map_err(|e| Error::new(&format!("Invalid UTF-8 in decrypted data: {}", e)))
}
pub fn derive_key_from_password(password: &str, salt: &[u8]) -> Result<Self> {
use pbkdf2::pbkdf2_hmac;
use sha2::Sha256;
let mut key = [0u8; 32];
pbkdf2_hmac::<Sha256>(password.as_bytes(), salt, 100_000, &mut key);
Ok(Self { key })
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VaultConfig {
pub address: String,
pub token: String,
pub mount_path: String,
}
pub struct VaultBackend {
config: VaultConfig,
client: reqwest::Client,
}
impl VaultBackend {
pub fn new(config: VaultConfig) -> Self {
Self {
config,
client: reqwest::Client::new(),
}
}
pub async fn store_secret(&self, key: &str, value: &str, metadata: &SecretMetadata) -> Result<()> {
let url = format!(
"{}/v1/{}/data/{}",
self.config.address, self.config.mount_path, key
);
let secret_type_str = format!("{:?}", metadata.secret_type);
let version_str = metadata.version.to_string();
let mut data = HashMap::new();
data.insert("value", value);
data.insert("secret_type", secret_type_str.as_str());
data.insert("version", version_str.as_str());
let mut payload = HashMap::new();
payload.insert("data", data);
let response = self
.client
.post(&url)
.header("X-Vault-Token", &self.config.token)
.json(&payload)
.send()
.await
.map_err(|e| Error::new(&format!("Vault request failed: {}", e)))?;
if !response.status().is_success() {
let status = response.status();
let body = response.text().await.unwrap_or_default();
return Err(Error::new(&format!(
"Vault request failed with status {}: {}",
status, body
)));
}
Ok(())
}
pub async fn get_secret(&self, key: &str) -> Result<(String, SecretMetadata)> {
let url = format!(
"{}/v1/{}/data/{}",
self.config.address, self.config.mount_path, key
);
let response = self
.client
.get(&url)
.header("X-Vault-Token", &self.config.token)
.send()
.await
.map_err(|e| Error::new(&format!("Vault request failed: {}", e)))?;
if !response.status().is_success() {
return Err(Error::new(&format!(
"Secret not found in Vault: {}",
key
)));
}
let json: serde_json::Value = response
.json()
.await
.map_err(|e| Error::new(&format!("Failed to parse Vault response: {}", e)))?;
let data = json
.get("data")
.and_then(|d| d.get("data"))
.ok_or_else(|| Error::new("Invalid Vault response format"))?;
let value = data
.get("value")
.and_then(|v| v.as_str())
.ok_or_else(|| Error::new("Missing 'value' in Vault response"))?
.to_string();
let secret_type = data
.get("secret_type")
.and_then(|v| v.as_str())
.unwrap_or("Generic");
let version = data
.get("version")
.and_then(|v| v.as_str())
.and_then(|v| v.parse().ok())
.unwrap_or(1);
let metadata = SecretMetadata {
secret_type: match secret_type {
"ApiKey" => SecretType::ApiKey,
"DatabaseCredential" => SecretType::DatabaseCredential,
"TlsCertificate" => SecretType::TlsCertificate,
"SigningKey" => SecretType::SigningKey,
_ => SecretType::Generic,
},
version,
created_at: Utc::now(),
last_rotated: None,
last_accessed: Some(Utc::now()),
};
Ok((value, metadata))
}
pub async fn delete_secret(&self, key: &str) -> Result<()> {
let url = format!(
"{}/v1/{}/data/{}",
self.config.address, self.config.mount_path, key
);
let response = self
.client
.delete(&url)
.header("X-Vault-Token", &self.config.token)
.send()
.await
.map_err(|e| Error::new(&format!("Vault request failed: {}", e)))?;
if !response.status().is_success() {
return Err(Error::new(&format!(
"Failed to delete secret from Vault: {}",
key
)));
}
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditLogEntry {
pub timestamp: DateTime<Utc>,
pub action: AuditAction,
pub secret_id: String,
pub result: AuditResult,
pub context: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AuditAction {
Store,
Retrieve,
Rotate,
Delete,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AuditResult {
Success,
Failure { error: String },
}
pub struct AuditLogger {
log_path: PathBuf,
buffer: Arc<RwLock<Vec<AuditLogEntry>>>,
}
impl AuditLogger {
pub fn new(log_path: impl AsRef<Path>) -> Self {
Self {
log_path: log_path.as_ref().to_path_buf(),
buffer: Arc::new(RwLock::new(Vec::new())),
}
}
pub async fn log(
&self,
action: AuditAction,
secret_id: String,
result: AuditResult,
context: HashMap<String, String>,
) -> Result<()> {
let entry = AuditLogEntry {
timestamp: Utc::now(),
action,
secret_id,
result,
context,
};
let mut buffer = self.buffer.write().await;
buffer.push(entry.clone());
let json = serde_json::to_string(&entry)
.map_err(|e| Error::new(&format!("Failed to serialize audit entry: {}", e)))?;
let mut file = tokio::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&self.log_path)
.await
.map_err(|e| Error::new(&format!("Failed to open audit log: {}", e)))?;
file.write_all(format!("{}\n", json).as_bytes())
.await
.map_err(|e| Error::new(&format!("Failed to write audit log: {}", e)))?;
Ok(())
}
pub async fn get_recent(&self, limit: usize) -> Result<Vec<AuditLogEntry>> {
let buffer = self.buffer.read().await;
let start = buffer.len().saturating_sub(limit);
Ok(buffer[start..].to_vec())
}
}
pub enum StorageBackend {
Local {
encryption: EncryptionProvider,
storage_path: PathBuf,
},
Vault {
backend: VaultBackend,
},
}
pub struct SecretsManager {
backend: StorageBackend,
audit_logger: AuditLogger,
cache: Arc<RwLock<HashMap<String, Secret<Encrypted>>>>,
}
impl SecretsManager {
pub fn with_encryption(key: &[u8]) -> Result<Self> {
let encryption = EncryptionProvider::new(key)?;
let storage_path = PathBuf::from(".ggen/secrets");
let audit_log_path = PathBuf::from(".ggen/audit/secrets.log");
Ok(Self {
backend: StorageBackend::Local {
encryption,
storage_path,
},
audit_logger: AuditLogger::new(audit_log_path),
cache: Arc::new(RwLock::new(HashMap::new())),
})
}
pub async fn with_vault(config: VaultConfig) -> Result<Self> {
let backend = VaultBackend::new(config);
let audit_log_path = PathBuf::from(".ggen/audit/secrets.log");
Ok(Self {
backend: StorageBackend::Vault { backend },
audit_logger: AuditLogger::new(audit_log_path),
cache: Arc::new(RwLock::new(HashMap::new())),
})
}
pub async fn store_secret(
&self,
key: &str,
value: &str,
secret_type: SecretType,
) -> Result<()> {
let secret = Secret::new(key.to_string(), value.to_string(), secret_type);
let result = match &self.backend {
StorageBackend::Local { encryption, .. } => {
let encrypted = secret.encrypt(encryption)?;
let mut cache = self.cache.write().await;
cache.insert(key.to_string(), encrypted);
Ok(())
}
StorageBackend::Vault { backend } => {
backend.store_secret(key, value, &secret.metadata).await
}
};
let audit_result = match &result {
Ok(()) => AuditResult::Success,
Err(e) => AuditResult::Failure {
error: e.to_string(),
},
};
self.audit_logger
.log(AuditAction::Store, key.to_string(), audit_result, HashMap::new())
.await?;
result
}
pub async fn get_secret(&self, key: &str) -> Result<Secret<Ready>> {
let result = match &self.backend {
StorageBackend::Local { encryption, .. } => {
let cache = self.cache.read().await;
let encrypted = cache
.get(key)
.ok_or_else(|| Error::new(&format!("Secret not found: {}", key)))?
.clone();
encrypted.decrypt(encryption)
}
StorageBackend::Vault { backend } => {
let (value, metadata) = backend.get_secret(key).await?;
let secret = Secret {
id: key.to_string(),
value: SecretValue::Plaintext(value),
metadata,
_state: PhantomData,
};
Ok(secret)
}
};
let audit_result = match &result {
Ok(_) => AuditResult::Success,
Err(e) => AuditResult::Failure {
error: e.to_string(),
},
};
self.audit_logger
.log(
AuditAction::Retrieve,
key.to_string(),
audit_result,
HashMap::new(),
)
.await?;
result
}
pub async fn rotate_secret(&self, key: &str, new_value: &str) -> Result<()> {
let secret = self.get_secret(key).await?;
let rotating = secret.begin_rotation();
let rotated = rotating.complete_rotation(new_value.to_string());
let result = match &self.backend {
StorageBackend::Local { encryption, .. } => {
let encrypted = rotated.encrypt(encryption)?;
let mut cache = self.cache.write().await;
cache.insert(key.to_string(), encrypted);
Ok(())
}
StorageBackend::Vault { backend } => {
backend
.store_secret(key, new_value, &rotated.metadata)
.await
}
};
let audit_result = match &result {
Ok(()) => AuditResult::Success,
Err(e) => AuditResult::Failure {
error: e.to_string(),
},
};
self.audit_logger
.log(
AuditAction::Rotate,
key.to_string(),
audit_result,
HashMap::new(),
)
.await?;
result
}
pub async fn delete_secret(&self, key: &str) -> Result<()> {
let result = match &self.backend {
StorageBackend::Local { .. } => {
let mut cache = self.cache.write().await;
cache.remove(key);
Ok(())
}
StorageBackend::Vault { backend } => backend.delete_secret(key).await,
};
let audit_result = match &result {
Ok(()) => AuditResult::Success,
Err(e) => AuditResult::Failure {
error: e.to_string(),
},
};
self.audit_logger
.log(
AuditAction::Delete,
key.to_string(),
audit_result,
HashMap::new(),
)
.await?;
result
}
pub async fn get_audit_log(&self, limit: usize) -> Result<Vec<AuditLogEntry>> {
self.audit_logger.get_recent(limit).await
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encryption_provider_creation_valid_key() {
let key = b"12345678901234567890123456789012";
let result = EncryptionProvider::new(key);
assert!(result.is_ok());
}
#[test]
fn test_encryption_provider_creation_invalid_key_length() {
let key = b"short_key";
let result = EncryptionProvider::new(key);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("32 bytes"));
}
#[test]
fn test_encrypt_decrypt_roundtrip() {
let key = b"12345678901234567890123456789012";
let provider = EncryptionProvider::new(key).expect("Failed to create provider");
let plaintext = "sensitive_secret_data";
let encrypted = provider.encrypt(plaintext).expect("Encryption failed");
let decrypted = provider.decrypt(&encrypted).expect("Decryption failed");
assert_eq!(decrypted, plaintext);
}
#[test]
fn test_encrypt_produces_different_ciphertext() {
let key = b"12345678901234567890123456789012";
let provider = EncryptionProvider::new(key).expect("Failed to create provider");
let plaintext = "secret";
let ciphertext1 = provider.encrypt(plaintext).expect("Encryption failed");
let ciphertext2 = provider.encrypt(plaintext).expect("Encryption failed");
assert_ne!(ciphertext1, ciphertext2);
}
#[test]
fn test_decrypt_tampered_data_fails() {
let key = b"12345678901234567890123456789012";
let provider = EncryptionProvider::new(key).expect("Failed to create provider");
let plaintext = "secret";
let mut encrypted = provider.encrypt(plaintext).expect("Encryption failed");
encrypted[20] ^= 0xFF;
let result = provider.decrypt(&encrypted);
assert!(result.is_err());
}
#[test]
fn test_decrypt_short_data_fails() {
let key = b"12345678901234567890123456789012";
let provider = EncryptionProvider::new(key).expect("Failed to create provider");
let short_data = vec![1, 2, 3];
let result = provider.decrypt(&short_data);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("too short"));
}
#[test]
fn test_derive_key_from_password() {
let password = "strong_password";
let salt = b"random_salt_12345678";
let provider = EncryptionProvider::derive_key_from_password(password, salt)
.expect("Key derivation failed");
let plaintext = "test_secret";
let encrypted = provider.encrypt(plaintext).expect("Encryption failed");
let decrypted = provider.decrypt(&encrypted).expect("Decryption failed");
assert_eq!(decrypted, plaintext);
}
#[test]
fn test_derive_key_deterministic() {
let password = "password";
let salt = b"salt12345678901234567890";
let provider1 = EncryptionProvider::derive_key_from_password(password, salt)
.expect("Key derivation failed");
let provider2 = EncryptionProvider::derive_key_from_password(password, salt)
.expect("Key derivation failed");
let plaintext = "test";
let encrypted = provider1.encrypt(plaintext).expect("Encryption failed");
let decrypted = provider2.decrypt(&encrypted).expect("Decryption failed");
assert_eq!(decrypted, plaintext);
}
#[test]
fn test_encrypt_empty_string() {
let key = b"12345678901234567890123456789012";
let provider = EncryptionProvider::new(key).expect("Failed to create provider");
let plaintext = "";
let encrypted = provider.encrypt(plaintext).expect("Encryption failed");
let decrypted = provider.decrypt(&encrypted).expect("Decryption failed");
assert_eq!(decrypted, plaintext);
}
#[test]
fn test_encrypt_unicode() {
let key = b"12345678901234567890123456789012";
let provider = EncryptionProvider::new(key).expect("Failed to create provider");
let plaintext = "こんにちは世界🔒";
let encrypted = provider.encrypt(plaintext).expect("Encryption failed");
let decrypted = provider.decrypt(&encrypted).expect("Decryption failed");
assert_eq!(decrypted, plaintext);
}
#[test]
fn test_encrypt_long_text() {
let key = b"12345678901234567890123456789012";
let provider = EncryptionProvider::new(key).expect("Failed to create provider");
let plaintext = "a".repeat(10000);
let encrypted = provider.encrypt(&plaintext).expect("Encryption failed");
let decrypted = provider.decrypt(&encrypted).expect("Decryption failed");
assert_eq!(decrypted, plaintext);
}
#[test]
fn test_secret_creation_plaintext() {
let secret = Secret::new(
"api_key".to_string(),
"secret_value".to_string(),
SecretType::ApiKey,
);
assert_eq!(secret.id(), "api_key");
assert_eq!(secret.metadata().secret_type, SecretType::ApiKey);
assert_eq!(secret.metadata().version, 1);
}
#[test]
fn test_secret_encrypt_state_transition() {
let key = b"12345678901234567890123456789012";
let provider = EncryptionProvider::new(key).expect("Failed to create provider");
let secret = Secret::new(
"test".to_string(),
"value".to_string(),
SecretType::Generic,
);
let encrypted = secret.encrypt(&provider).expect("Encryption failed");
assert_eq!(encrypted.id(), "test");
assert_eq!(encrypted.metadata().version, 1);
}
#[test]
fn test_secret_decrypt_state_transition() {
let key = b"12345678901234567890123456789012";
let provider = EncryptionProvider::new(key).expect("Failed to create provider");
let secret = Secret::new(
"test".to_string(),
"value".to_string(),
SecretType::Generic,
);
let encrypted = secret.encrypt(&provider).expect("Encryption failed");
let ready = encrypted.decrypt(&provider).expect("Decryption failed");
assert_eq!(ready.id(), "test");
assert_eq!(ready.value(), "value");
}
#[test]
fn test_secret_rotation_state_transition() {
let secret = Secret::new(
"test".to_string(),
"old_value".to_string(),
SecretType::DatabaseCredential,
);
let rotating = secret.begin_rotation();
let rotated = rotating.complete_rotation("new_value".to_string());
assert_eq!(rotated.value.as_plaintext(), "new_value");
assert_eq!(rotated.metadata.version, 2);
assert!(rotated.metadata.last_rotated.is_some());
}
#[test]
fn test_secret_metadata_immutable() {
let secret = Secret::new(
"test".to_string(),
"value".to_string(),
SecretType::SigningKey,
);
let metadata = secret.metadata();
assert_eq!(metadata.secret_type, SecretType::SigningKey);
assert_eq!(metadata.version, 1);
assert!(metadata.last_rotated.is_none());
}
#[tokio::test]
async fn test_audit_logger_creation() {
let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
let log_path = temp_dir.path().join("audit.log");
let logger = AuditLogger::new(&log_path);
assert_eq!(logger.log_path, log_path);
}
#[tokio::test]
async fn test_audit_logger_log_success() {
let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
let log_path = temp_dir.path().join("audit.log");
let logger = AuditLogger::new(&log_path);
let result = logger
.log(
AuditAction::Store,
"test_secret".to_string(),
AuditResult::Success,
HashMap::new(),
)
.await;
assert!(result.is_ok());
assert!(log_path.exists());
}
#[tokio::test]
async fn test_audit_logger_get_recent() {
let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
let log_path = temp_dir.path().join("audit.log");
let logger = AuditLogger::new(&log_path);
logger
.log(
AuditAction::Store,
"secret1".to_string(),
AuditResult::Success,
HashMap::new(),
)
.await
.expect("Log failed");
logger
.log(
AuditAction::Retrieve,
"secret2".to_string(),
AuditResult::Success,
HashMap::new(),
)
.await
.expect("Log failed");
let entries = logger.get_recent(2).await.expect("Get recent failed");
assert_eq!(entries.len(), 2);
}
#[tokio::test]
async fn test_secrets_manager_store_and_retrieve() {
let key = b"12345678901234567890123456789012";
let manager = SecretsManager::with_encryption(key).expect("Failed to create manager");
manager
.store_secret("github_token", "ghp_1234567890", SecretType::ApiKey)
.await
.expect("Store failed");
let secret = manager
.get_secret("github_token")
.await
.expect("Retrieve failed");
assert_eq!(secret.value(), "ghp_1234567890");
assert_eq!(secret.metadata().secret_type, SecretType::ApiKey);
}
#[tokio::test]
async fn test_secrets_manager_rotate_secret() {
let key = b"12345678901234567890123456789012";
let manager = SecretsManager::with_encryption(key).expect("Failed to create manager");
manager
.store_secret("db_password", "old_password", SecretType::DatabaseCredential)
.await
.expect("Store failed");
manager
.rotate_secret("db_password", "new_password")
.await
.expect("Rotation failed");
let secret = manager
.get_secret("db_password")
.await
.expect("Retrieve failed");
assert_eq!(secret.value(), "new_password");
assert_eq!(secret.metadata().version, 2);
}
#[tokio::test]
async fn test_secrets_manager_delete_secret() {
let key = b"12345678901234567890123456789012";
let manager = SecretsManager::with_encryption(key).expect("Failed to create manager");
manager
.store_secret("temp_key", "temp_value", SecretType::Generic)
.await
.expect("Store failed");
manager
.delete_secret("temp_key")
.await
.expect("Delete failed");
let result = manager.get_secret("temp_key").await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_secrets_manager_get_audit_log() {
let key = b"12345678901234567890123456789012";
let manager = SecretsManager::with_encryption(key).expect("Failed to create manager");
manager
.store_secret("key1", "value1", SecretType::ApiKey)
.await
.expect("Store failed");
manager
.get_secret("key1")
.await
.expect("Retrieve failed");
let entries = manager.get_audit_log(10).await.expect("Get audit log failed");
assert!(entries.len() >= 2);
}
}