1use crate::audit;
7use crate::auth;
8use crate::error::{AuthyError, Result};
9use crate::vault::{self, Vault, VaultKey};
10use crate::vault::secret::SecretEntry;
11
12pub struct AuthyClient {
17 key: VaultKey,
18 audit_key: Vec<u8>,
20 actor: String,
22}
23
24impl AuthyClient {
25 pub fn with_passphrase(passphrase: &str) -> Result<Self> {
27 let key = VaultKey::Passphrase(passphrase.to_string());
28 let material = audit::key_material(&key);
29 let audit_key = audit::derive_audit_key(&material);
30 Ok(Self {
31 key,
32 audit_key,
33 actor: "api(passphrase)".to_string(),
34 })
35 }
36
37 pub fn with_keyfile(keyfile_path: &str) -> Result<Self> {
39 let (identity, pubkey) = auth::read_keyfile(keyfile_path)?;
40 let key = VaultKey::Keyfile { identity, pubkey };
41 let material = audit::key_material(&key);
42 let audit_key = audit::derive_audit_key(&material);
43 Ok(Self {
44 key,
45 audit_key,
46 actor: "api(keyfile)".to_string(),
47 })
48 }
49
50 pub fn from_env() -> Result<Self> {
54 if let Ok(keyfile_path) = std::env::var("AUTHY_KEYFILE") {
55 return Self::with_keyfile(&keyfile_path);
56 }
57 if let Ok(passphrase) = std::env::var("AUTHY_PASSPHRASE") {
58 return Self::with_passphrase(&passphrase);
59 }
60 Err(AuthyError::AuthFailed(
61 "No credentials found. Set AUTHY_KEYFILE or AUTHY_PASSPHRASE.".into(),
62 ))
63 }
64
65 pub fn with_actor(mut self, actor: impl Into<String>) -> Self {
67 self.actor = actor.into();
68 self
69 }
70
71 pub fn is_initialized() -> bool {
73 vault::is_initialized()
74 }
75
76 pub fn get(&self, name: &str) -> Result<Option<String>> {
78 let v = vault::load_vault(&self.key)?;
79
80 let result = v.secrets.get(name).map(|e| e.value.clone());
81 let outcome = if result.is_some() { "success" } else { "not_found" };
82
83 self.audit("get", Some(name), outcome, None);
84 Ok(result)
85 }
86
87 pub fn get_or_err(&self, name: &str) -> Result<String> {
89 self.get(name)?
90 .ok_or_else(|| AuthyError::SecretNotFound(name.to_string()))
91 }
92
93 pub fn store(&self, name: &str, value: &str, force: bool) -> Result<()> {
96 let mut v = vault::load_vault(&self.key)?;
97
98 if !force && v.secrets.contains_key(name) {
99 self.audit("store", Some(name), "denied", Some("already exists"));
100 return Err(AuthyError::SecretAlreadyExists(name.to_string()));
101 }
102
103 let is_update = v.secrets.contains_key(name);
104 v.secrets
105 .insert(name.to_string(), SecretEntry::new(value.to_string()));
106 v.touch();
107 vault::save_vault(&v, &self.key)?;
108
109 let op = if is_update { "update" } else { "store" };
110 self.audit(op, Some(name), "success", None);
111 Ok(())
112 }
113
114 pub fn remove(&self, name: &str) -> Result<bool> {
116 let mut v = vault::load_vault(&self.key)?;
117
118 let existed = v.secrets.remove(name).is_some();
119 if existed {
120 v.touch();
121 vault::save_vault(&v, &self.key)?;
122 self.audit("remove", Some(name), "success", None);
123 } else {
124 self.audit("remove", Some(name), "not_found", None);
125 }
126
127 Ok(existed)
128 }
129
130 pub fn rotate(&self, name: &str, new_value: &str) -> Result<u32> {
133 let mut v = vault::load_vault(&self.key)?;
134
135 let entry = v
136 .secrets
137 .get_mut(name)
138 .ok_or_else(|| AuthyError::SecretNotFound(name.to_string()))?;
139
140 entry.value = new_value.to_string();
141 entry.metadata.bump_version();
142 let version = entry.metadata.version;
143
144 v.touch();
145 vault::save_vault(&v, &self.key)?;
146
147 self.audit(
148 "rotate",
149 Some(name),
150 "success",
151 Some(&format!("v{version}")),
152 );
153 Ok(version)
154 }
155
156 pub fn list(&self, scope: Option<&str>) -> Result<Vec<String>> {
158 let v = vault::load_vault(&self.key)?;
159
160 let names: Vec<String> = if let Some(scope_name) = scope {
161 let policy = v
162 .policies
163 .get(scope_name)
164 .ok_or_else(|| AuthyError::PolicyNotFound(scope_name.to_string()))?;
165 let all_names: Vec<&str> = v.secrets.keys().map(String::as_str).collect();
166 policy
167 .filter_secrets(&all_names)?
168 .into_iter()
169 .map(String::from)
170 .collect()
171 } else {
172 v.secrets.keys().cloned().collect()
173 };
174
175 self.audit("list", None, "success", None);
176 Ok(names)
177 }
178
179 pub fn init_vault(&self) -> Result<()> {
181 if vault::is_initialized() {
182 return Err(AuthyError::VaultAlreadyExists(
183 vault::vault_path().display().to_string(),
184 ));
185 }
186 let v = Vault::new();
187 vault::save_vault(&v, &self.key)?;
188
189 let config = crate::config::Config::default();
191 config.save(&vault::config_path())?;
192
193 self.audit("init", None, "success", None);
194 Ok(())
195 }
196
197 pub fn audit_entries(&self) -> Result<Vec<audit::AuditEntry>> {
199 audit::read_entries(&vault::audit_path())
200 }
201
202 pub fn verify_audit_chain(&self) -> Result<(usize, bool)> {
205 audit::verify_chain(&vault::audit_path(), &self.audit_key)
206 }
207
208 fn audit(&self, operation: &str, secret: Option<&str>, outcome: &str, detail: Option<&str>) {
211 let _ = audit::log_event(
212 &vault::audit_path(),
213 operation,
214 secret,
215 &self.actor,
216 outcome,
217 detail,
218 &self.audit_key,
219 );
220 }
221}