doum_cli/system/
secret.rs

1use anyhow::{Context, Result};
2use keyring::Entry;
3use serde::{Deserialize, Serialize};
4
5/// Trait for provider-specific secret implementations
6pub trait ProviderSecret: Serialize + for<'de> Deserialize<'de> {
7    /// Validate the secret before saving
8    fn validate(&self) -> Result<()>;
9
10    /// Get masked version for display
11    fn masked(&self) -> String;
12}
13
14/// Constant service name for keyring entries
15const SECRET_SERVICE_NAME: &str = "doum-cli";
16
17/// Secret manager for handling keyring operations
18pub struct SecretManager;
19
20impl SecretManager {
21    /// Save a secret value to the system keyring
22    pub fn save<T: ProviderSecret>(service_name: &str, secret: &T) -> Result<()> {
23        // Validate secret
24        secret.validate()?;
25
26        // Save to Keyring
27        let entry =
28            Entry::new(service_name, SECRET_SERVICE_NAME).context("Failed to access keyring")?;
29        let value = serde_json::to_string(secret).context("Failed to serialize secret to JSON")?;
30        entry
31            .set_password(&value)
32            .context("Failed to save to keyring")?;
33
34        Ok(())
35    }
36
37    /// Load secret from Keyring
38    pub fn load<T: ProviderSecret>(service_name: &str) -> Result<T> {
39        let entry =
40            Entry::new(service_name, SECRET_SERVICE_NAME).context("Failed to access keyring")?;
41
42        let secret_json = entry
43            .get_password()
44            .context("Failed to retrieve from keyring")?;
45
46        serde_json::from_str(&secret_json).context("Failed to parse secret")
47    }
48
49    /// Delete secret from Keyring
50    pub fn delete(service_name: &str) -> Result<()> {
51        let entry =
52            Entry::new(service_name, SECRET_SERVICE_NAME).context("Failed to access keyring")?;
53        entry
54            .delete_credential()
55            .context("Failed to delete from keyring")?;
56        Ok(())
57    }
58}