doum_cli/system/
secret.rs1use crate::llm::Provider;
2use anyhow::Result;
3use keyring::Entry;
4use serde::{Deserialize, Serialize};
5
6pub trait ProviderSecret: Serialize + for<'de> Deserialize<'de> {
8 fn validate(&self) -> Result<()>;
10
11 fn masked(&self) -> String;
13}
14
15const SECRET_SERVICE_NAME: &str = "doum-cli";
17
18pub struct SecretManager;
20
21impl SecretManager {
22 pub fn save<T: ProviderSecret>(provider: &Provider, secret: &T) -> Result<()> {
24 secret.validate()?;
26
27 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 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 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}