doum_cli/system/
secret.rs

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