1use anyhow::{Context, Result};
4use securestore::KeySource;
5use totp_rs::{Algorithm, Secret as TotpSecret, TOTP};
6
7use super::SecretsManager;
8use super::types::{AccessContext, AccessPolicy, CredentialValue, SecretEntry, SecretKind};
9
10impl SecretsManager {
11 pub(super) fn ensure_vault(&mut self) -> Result<&mut securestore::SecretsManager> {
13 if self.vault.is_none() {
14 let vault = if self.vault_path.exists() {
15 if let Some(ref pw) = self.password {
17 securestore::SecretsManager::load(&self.vault_path, KeySource::Password(pw))
18 .context("Failed to load secrets vault (wrong password?)")?
19 } else if self.key_path.exists() {
20 securestore::SecretsManager::load(
21 &self.vault_path,
22 KeySource::from_file(&self.key_path),
23 )
24 .context("Failed to load secrets vault")?
25 } else {
26 anyhow::bail!(
27 "Secrets vault exists but no key file or password provided. \
28 Run `rustyclaw onboard` to configure."
29 );
30 }
31 } else {
32 if let Some(parent) = self.vault_path.parent() {
34 std::fs::create_dir_all(parent)
35 .context("Failed to create secrets directory")?;
36 }
37 if let Some(ref pw) = self.password {
38 let sman = securestore::SecretsManager::new(KeySource::Password(pw))
40 .context("Failed to create new secrets vault")?;
41 sman.save_as(&self.vault_path)
42 .context("Failed to save new secrets vault")?;
43 securestore::SecretsManager::load(&self.vault_path, KeySource::Password(pw))
44 .context("Failed to reload newly-created secrets vault")?
45 } else {
46 let sman = securestore::SecretsManager::new(KeySource::Csprng)
48 .context("Failed to create new secrets vault")?;
49 sman.export_key(&self.key_path)
50 .context("Failed to export secrets key")?;
51 sman.save_as(&self.vault_path)
52 .context("Failed to save new secrets vault")?;
53 securestore::SecretsManager::load(
54 &self.vault_path,
55 KeySource::from_file(&self.key_path),
56 )
57 .context("Failed to reload newly-created secrets vault")?
58 }
59 };
60 self.vault = Some(vault);
61 }
62 Ok(self.vault.as_mut().unwrap())
64 }
65
66 pub fn change_password(&mut self, new_password: String) -> Result<()> {
73 let old_vault = self.ensure_vault()?;
75
76 let keys: Vec<String> = old_vault.keys().map(|s| s.to_string()).collect();
78 let mut entries: Vec<(String, String)> = Vec::new();
79 for key in &keys {
80 match old_vault.get(key) {
81 Ok(value) => entries.push((key.clone(), value)),
82 Err(_) => {}
84 }
85 }
86
87 self.vault = None;
89
90 let new_vault = securestore::SecretsManager::new(KeySource::Password(&new_password))
91 .context("Failed to create vault with new password")?;
92 new_vault
93 .save_as(&self.vault_path)
94 .context("Failed to save re-encrypted vault")?;
95
96 let mut reloaded =
98 securestore::SecretsManager::load(&self.vault_path, KeySource::Password(&new_password))
99 .context("Failed to reload vault with new password")?;
100
101 for (key, value) in entries {
103 reloaded.set(&key, value);
104 }
105 reloaded.save().context("Failed to save re-keyed vault")?;
106
107 self.password = Some(new_password);
109 self.vault = Some(reloaded);
110
111 if self.key_path.exists() {
113 let _ = std::fs::remove_file(&self.key_path);
114 }
115
116 Ok(())
117 }
118
119 pub fn store_secret(&mut self, key: &str, value: &str) -> Result<()> {
123 let vault = self.ensure_vault()?;
124 vault.set(key, value);
125 vault.save().context("Failed to save secrets vault")?;
126 Ok(())
127 }
128
129 pub fn get_secret(&mut self, key: &str, user_approved: bool) -> Result<Option<String>> {
135 if !self.agent_access_enabled && !user_approved {
136 return Ok(None);
137 }
138
139 let vault = self.ensure_vault()?;
140 match vault.get(key) {
141 Ok(value) => Ok(Some(value)),
142 Err(e) if e.kind() == securestore::ErrorKind::SecretNotFound => Ok(None),
143 Err(e) => Err(anyhow::anyhow!("Failed to get secret: {}", e)),
144 }
145 }
146
147 pub fn delete_secret(&mut self, key: &str) -> Result<()> {
149 let vault = self.ensure_vault()?;
150 vault.remove(key).context("Failed to remove secret")?;
151 vault.save().context("Failed to save secrets vault")?;
152 Ok(())
153 }
154
155 pub fn list_secrets(&mut self) -> Vec<String> {
157 match self.ensure_vault() {
158 Ok(vault) => vault.keys().map(|s| s.to_string()).collect(),
159 Err(_) => Vec::new(),
160 }
161 }
162
163 pub fn store_credential(
174 &mut self,
175 name: &str,
176 entry: &SecretEntry,
177 value: &str,
178 username: Option<&str>,
179 ) -> Result<()> {
180 let meta_key = format!("cred:{}", name);
181 let val_key = format!("val:{}", name);
182
183 let meta_json =
184 serde_json::to_string(entry).context("Failed to serialize credential metadata")?;
185 self.store_secret(&meta_key, &meta_json)?;
186 self.store_secret(&val_key, value)?;
187
188 if entry.kind == SecretKind::UsernamePassword {
189 let user_key = format!("val:{}:user", name);
190 self.store_secret(&user_key, username.unwrap_or(""))?;
191 }
192
193 Ok(())
194 }
195
196 pub fn store_form_autofill(
202 &mut self,
203 name: &str,
204 entry: &SecretEntry,
205 fields: &std::collections::BTreeMap<String, String>,
206 ) -> Result<()> {
207 debug_assert_eq!(entry.kind, SecretKind::FormAutofill);
208
209 let meta_key = format!("cred:{}", name);
210 let fields_key = format!("val:{}:fields", name);
211
212 let meta_json =
213 serde_json::to_string(entry).context("Failed to serialize credential metadata")?;
214 let fields_json =
215 serde_json::to_string(fields).context("Failed to serialize form fields")?;
216
217 self.store_secret(&meta_key, &meta_json)?;
218 self.store_secret(&fields_key, &fields_json)?;
219 Ok(())
220 }
221
222 pub fn store_payment_method(
224 &mut self,
225 name: &str,
226 entry: &SecretEntry,
227 cardholder: &str,
228 number: &str,
229 expiry: &str,
230 cvv: &str,
231 extra: &std::collections::BTreeMap<String, String>,
232 ) -> Result<()> {
233 debug_assert_eq!(entry.kind, SecretKind::PaymentMethod);
234
235 let meta_key = format!("cred:{}", name);
236 let card_key = format!("val:{}:card", name);
237 let extra_key = format!("val:{}:card_extra", name);
238
239 let meta_json =
240 serde_json::to_string(entry).context("Failed to serialize credential metadata")?;
241
242 #[derive(serde::Serialize)]
243 struct Card<'a> {
244 cardholder: &'a str,
245 number: &'a str,
246 expiry: &'a str,
247 cvv: &'a str,
248 }
249 let card_json = serde_json::to_string(&Card {
250 cardholder,
251 number,
252 expiry,
253 cvv,
254 })
255 .context("Failed to serialize card details")?;
256
257 self.store_secret(&meta_key, &meta_json)?;
258 self.store_secret(&card_key, &card_json)?;
259
260 if !extra.is_empty() {
261 let extra_json =
262 serde_json::to_string(extra).context("Failed to serialize card extras")?;
263 self.store_secret(&extra_key, &extra_json)?;
264 }
265
266 Ok(())
267 }
268
269 pub fn get_credential(
279 &mut self,
280 name: &str,
281 ctx: &AccessContext,
282 ) -> Result<Option<(SecretEntry, CredentialValue)>> {
283 let meta_key = format!("cred:{}", name);
284 let val_key = format!("val:{}", name);
285
286 let meta_json = match self.get_secret(&meta_key, true)? {
288 Some(j) => j,
289 None => return Ok(None),
290 };
291 let entry: SecretEntry =
292 serde_json::from_str(&meta_json).context("Corrupted credential metadata")?;
293
294 if entry.disabled {
296 anyhow::bail!("Credential '{}' is disabled", name,);
297 }
298
299 if !self.check_access(&entry.policy, ctx) {
301 anyhow::bail!(
302 "Access denied for credential '{}' (policy: {:?})",
303 name,
304 entry.policy,
305 );
306 }
307
308 let value = match entry.kind {
310 SecretKind::UsernamePassword => {
311 let password = self.get_secret(&val_key, true)?.unwrap_or_default();
312 let user_key = format!("val:{}:user", name);
313 let username = self.get_secret(&user_key, true)?.unwrap_or_default();
314 CredentialValue::UserPass { username, password }
315 }
316 SecretKind::SshKey => {
317 let private_key = self.get_secret(&val_key, true)?.unwrap_or_default();
318 let pub_key = format!("val:{}:pub", name);
319 let public_key = self.get_secret(&pub_key, true)?.unwrap_or_default();
320 CredentialValue::SshKeyPair {
321 private_key,
322 public_key,
323 }
324 }
325 SecretKind::FormAutofill => {
326 let fields_key = format!("val:{}:fields", name);
327 let fields_json = self
328 .get_secret(&fields_key, true)?
329 .unwrap_or_else(|| "{}".to_string());
330 let fields: std::collections::BTreeMap<String, String> =
331 serde_json::from_str(&fields_json).context("Corrupted form-autofill fields")?;
332 CredentialValue::FormFields(fields)
333 }
334 SecretKind::PaymentMethod => {
335 let card_key = format!("val:{}:card", name);
336 let extra_key = format!("val:{}:card_extra", name);
337
338 let card_json = self
339 .get_secret(&card_key, true)?
340 .unwrap_or_else(|| "{}".to_string());
341
342 #[derive(serde::Deserialize)]
343 struct Card {
344 #[serde(default)]
345 cardholder: String,
346 #[serde(default)]
347 number: String,
348 #[serde(default)]
349 expiry: String,
350 #[serde(default)]
351 cvv: String,
352 }
353 let card: Card =
354 serde_json::from_str(&card_json).context("Corrupted payment card data")?;
355
356 let extra: std::collections::BTreeMap<String, String> =
357 match self.get_secret(&extra_key, true)? {
358 Some(j) => serde_json::from_str(&j).context("Corrupted card extras")?,
359 None => std::collections::BTreeMap::new(),
360 };
361
362 CredentialValue::PaymentCard {
363 cardholder: card.cardholder,
364 number: card.number,
365 expiry: card.expiry,
366 cvv: card.cvv,
367 extra,
368 }
369 }
370 _ => {
371 let v = self.get_secret(&val_key, true)?.unwrap_or_default();
372 CredentialValue::Single(v)
373 }
374 };
375
376 Ok(Some((entry, value)))
377 }
378
379 pub fn list_credentials(&mut self) -> Vec<(String, SecretEntry)> {
381 let keys = self.list_secrets();
382 let mut result = Vec::new();
383 for key in &keys {
384 if let Some(name) = key.strip_prefix("cred:") {
385 if let Ok(Some(json)) = self.get_secret(key, true) {
386 if let Ok(entry) = serde_json::from_str::<SecretEntry>(&json) {
387 result.push((name.to_string(), entry));
388 }
389 }
390 }
391 }
392 result
393 }
394
395 pub fn list_all_entries(&mut self) -> Vec<(String, SecretEntry)> {
403 let all_keys = self.list_secrets();
404
405 let mut result = Vec::new();
406 let mut typed_names: std::collections::HashSet<String> = std::collections::HashSet::new();
407
408 for key in &all_keys {
410 if let Some(name) = key.strip_prefix("cred:") {
411 if let Ok(Some(json)) = self.get_secret(key, true) {
412 if let Ok(entry) = serde_json::from_str::<SecretEntry>(&json) {
413 typed_names.insert(name.to_string());
414 result.push((name.to_string(), entry));
415 }
416 }
417 }
418 }
419
420 for key in &all_keys {
422 if key.starts_with("cred:")
424 || key.starts_with("val:")
425 || key == Self::TOTP_SECRET_KEY
426 || key == "__init"
427 {
428 continue;
429 }
430 if typed_names.contains(key.as_str()) {
432 continue;
433 }
434
435 let (label, kind) = Self::label_for_legacy_key(key);
437 result.push((
438 key.clone(),
439 SecretEntry {
440 label,
441 kind,
442 policy: AccessPolicy::WithApproval,
443 description: None,
444 disabled: false,
445 },
446 ));
447 }
448
449 result
450 }
451
452 pub(super) fn label_for_legacy_key(key: &str) -> (String, SecretKind) {
455 use crate::providers::PROVIDERS;
456 for p in PROVIDERS {
458 if p.secret_key == Some(key) {
459 let kind = match p.auth_method {
460 crate::providers::AuthMethod::DeviceFlow => SecretKind::Token,
461 _ => SecretKind::ApiKey,
462 };
463 return (p.display.to_string(), kind);
464 }
465 }
466 let label = key
468 .replace('_', " ")
469 .to_lowercase()
470 .split(' ')
471 .map(|w| {
472 let mut c = w.chars();
473 match c.next() {
474 Some(first) => {
475 let upper: String = first.to_uppercase().collect();
476 format!("{}{}", upper, c.as_str())
477 }
478 None => String::new(),
479 }
480 })
481 .collect::<Vec<_>>()
482 .join(" ");
483 (label, SecretKind::Other)
484 }
485
486 pub fn peek_credential_display(&mut self, name: &str) -> Result<Vec<(String, String)>> {
494 let meta_key = format!("cred:{}", name);
495 let val_key = format!("val:{}", name);
496
497 if let Some(json) = self.get_secret(&meta_key, true)? {
499 let entry: SecretEntry =
500 serde_json::from_str(&json).context("Corrupted credential metadata")?;
501
502 let pairs = match entry.kind {
503 SecretKind::UsernamePassword => {
504 let password = self.get_secret(&val_key, true)?.unwrap_or_default();
505 let user_key = format!("val:{}:user", name);
506 let username = self.get_secret(&user_key, true)?.unwrap_or_default();
507 vec![
508 ("Username".to_string(), username),
509 ("Password".to_string(), password),
510 ]
511 }
512 SecretKind::SshKey => {
513 let private_key = self.get_secret(&val_key, true)?.unwrap_or_default();
514 let pub_key = format!("val:{}:pub", name);
515 let public_key = self.get_secret(&pub_key, true)?.unwrap_or_default();
516 vec![
517 ("Public Key".to_string(), public_key),
518 ("Private Key".to_string(), private_key),
519 ]
520 }
521 SecretKind::FormAutofill => {
522 let fields_key = format!("val:{}:fields", name);
523 let fields_json = self
524 .get_secret(&fields_key, true)?
525 .unwrap_or_else(|| "{}".to_string());
526 let fields: std::collections::BTreeMap<String, String> =
527 serde_json::from_str(&fields_json).unwrap_or_default();
528 fields.into_iter().collect()
529 }
530 SecretKind::PaymentMethod => {
531 let card_key = format!("val:{}:card", name);
532 let card_json = self
533 .get_secret(&card_key, true)?
534 .unwrap_or_else(|| "{}".to_string());
535
536 #[derive(serde::Deserialize)]
537 struct Card {
538 #[serde(default)]
539 cardholder: String,
540 #[serde(default)]
541 number: String,
542 #[serde(default)]
543 expiry: String,
544 #[serde(default)]
545 cvv: String,
546 }
547 let card: Card = serde_json::from_str(&card_json).unwrap_or(Card {
548 cardholder: String::new(),
549 number: String::new(),
550 expiry: String::new(),
551 cvv: String::new(),
552 });
553
554 let mut pairs = vec![
555 ("Cardholder".to_string(), card.cardholder),
556 ("Number".to_string(), card.number),
557 ("Expiry".to_string(), card.expiry),
558 ("CVV".to_string(), card.cvv),
559 ];
560
561 let extra_key = format!("val:{}:card_extra", name);
562 if let Some(j) = self.get_secret(&extra_key, true)? {
563 let extra: std::collections::BTreeMap<String, String> =
564 serde_json::from_str(&j).unwrap_or_default();
565 for (k, v) in extra {
566 pairs.push((k, v));
567 }
568 }
569 pairs
570 }
571 _ => {
572 let v = self.get_secret(&val_key, true)?.unwrap_or_default();
573 vec![("Value".to_string(), v)]
574 }
575 };
576 return Ok(pairs);
577 }
578
579 match self.get_secret(name, true)? {
581 Some(v) => Ok(vec![("Value".to_string(), v)]),
582 None => anyhow::bail!("Secret '{}' not found", name),
583 }
584 }
585
586 pub fn delete_credential(&mut self, name: &str) -> Result<()> {
588 let sub_keys = [
590 format!("cred:{}", name),
591 format!("val:{}", name),
592 format!("val:{}:user", name),
593 format!("val:{}:pub", name),
594 format!("val:{}:fields", name),
595 format!("val:{}:card", name),
596 format!("val:{}:card_extra", name),
597 ];
598 for key in &sub_keys {
599 let _ = self.delete_secret(key);
600 }
601
602 Ok(())
603 }
604
605 pub fn set_credential_disabled(&mut self, name: &str, disabled: bool) -> Result<()> {
612 let meta_key = format!("cred:{}", name);
613
614 let mut entry: SecretEntry = match self.get_secret(&meta_key, true)? {
615 Some(json) => serde_json::from_str(&json).context("Corrupted credential metadata")?,
616 None => {
617 let (label, kind) = Self::label_for_legacy_key(name);
619 SecretEntry {
620 label,
621 kind,
622 policy: AccessPolicy::WithApproval,
623 description: None,
624 disabled: false,
625 }
626 }
627 };
628
629 entry.disabled = disabled;
630
631 let meta_json =
632 serde_json::to_string(&entry).context("Failed to serialize credential metadata")?;
633 self.store_secret(&meta_key, &meta_json)?;
634 Ok(())
635 }
636
637 pub fn set_credential_policy(&mut self, name: &str, policy: AccessPolicy) -> Result<()> {
639 let meta_key = format!("cred:{}", name);
640
641 let mut entry: SecretEntry = match self.get_secret(&meta_key, true)? {
642 Some(json) => serde_json::from_str(&json).context("Corrupted credential metadata")?,
643 None => {
644 let (label, kind) = Self::label_for_legacy_key(name);
646 SecretEntry {
647 label,
648 kind,
649 policy: AccessPolicy::WithApproval,
650 description: None,
651 disabled: false,
652 }
653 }
654 };
655
656 entry.policy = policy;
657
658 let meta_json =
659 serde_json::to_string(&entry).context("Failed to serialize credential metadata")?;
660 self.store_secret(&meta_key, &meta_json)?;
661 Ok(())
662 }
663
664 pub fn generate_ssh_key(
671 &mut self,
672 name: &str,
673 comment: &str,
674 policy: AccessPolicy,
675 ) -> Result<String> {
676 use ssh_key::private::PrivateKey;
677
678 let private =
680 PrivateKey::random(&mut ssh_key::rand_core::OsRng, ssh_key::Algorithm::Ed25519)
681 .map_err(|e| anyhow::anyhow!("Failed to generate SSH key: {}", e))?;
682
683 let private_pem = private
684 .to_openssh(ssh_key::LineEnding::LF)
685 .map_err(|e| anyhow::anyhow!("Failed to encode private key: {}", e))?;
686
687 let public = private.public_key();
688 let public_openssh = public
689 .to_openssh()
690 .map_err(|e| anyhow::anyhow!("Failed to encode public key: {}", e))?;
691
692 let public_str = if comment.is_empty() {
693 public_openssh.to_string()
694 } else {
695 format!("{} {}", public_openssh, comment)
696 };
697
698 let entry = SecretEntry {
700 label: format!("SSH key ({})", name),
701 kind: SecretKind::SshKey,
702 policy,
703 description: Some(format!("Ed25519 keypair — {}", comment)),
704 disabled: false,
705 };
706
707 let meta_key = format!("cred:{}", name);
708 let val_key = format!("val:{}", name);
709 let pub_vault_key = format!("val:{}:pub", name);
710
711 let meta_json =
712 serde_json::to_string(&entry).context("Failed to serialize credential metadata")?;
713 self.store_secret(&meta_key, &meta_json)?;
714 self.store_secret(&val_key, private_pem.to_string().as_str())?;
715 self.store_secret(&pub_vault_key, &public_str)?;
716
717 Ok(public_str)
718 }
719
720 pub(super) fn check_access(&self, policy: &AccessPolicy, ctx: &AccessContext) -> bool {
725 match policy {
726 AccessPolicy::Always => true,
727 AccessPolicy::WithApproval => ctx.user_approved || self.agent_access_enabled,
728 AccessPolicy::WithAuth => ctx.authenticated,
729 AccessPolicy::SkillOnly(allowed) => {
730 if let Some(ref skill) = ctx.active_skill {
731 allowed.iter().any(|s| s == skill)
732 } else {
733 false
734 }
735 }
736 }
737 }
738
739 pub(super) const TOTP_SECRET_KEY: &'static str = "__rustyclaw_totp_secret";
743
744 pub fn setup_totp(&mut self, account_name: &str) -> Result<String> {
748 self.setup_totp_with_issuer(account_name, "RustyClaw")
749 }
750
751 pub fn setup_totp_with_issuer(&mut self, account_name: &str, issuer: &str) -> Result<String> {
754 let secret = TotpSecret::generate_secret();
755 let secret_bytes = secret
756 .to_bytes()
757 .map_err(|e| anyhow::anyhow!("Failed to generate TOTP secret bytes: {:?}", e))?;
758
759 let totp = TOTP::new(
760 Algorithm::SHA1,
761 6, 1, 30, secret_bytes,
765 Some(issuer.to_string()),
766 account_name.to_string(),
767 )
768 .map_err(|e| anyhow::anyhow!("Failed to create TOTP: {:?}", e))?;
769
770 let encoded = secret.to_encoded().to_string();
772 self.store_secret(Self::TOTP_SECRET_KEY, &encoded)?;
773
774 Ok(totp.get_url())
775 }
776
777 pub fn verify_totp(&mut self, code: &str) -> Result<bool> {
781 let encoded = self
782 .get_secret(Self::TOTP_SECRET_KEY, true)?
783 .ok_or_else(|| anyhow::anyhow!("No TOTP secret configured"))?;
784
785 let secret = TotpSecret::Encoded(encoded);
786 let secret_bytes = secret
787 .to_bytes()
788 .map_err(|e| anyhow::anyhow!("Corrupted TOTP secret: {:?}", e))?;
789
790 let totp = TOTP::new(
791 Algorithm::SHA1,
792 6,
793 1,
794 30,
795 secret_bytes,
796 Some("RustyClaw".to_string()),
797 String::new(),
798 )
799 .map_err(|e| anyhow::anyhow!("Failed to create TOTP: {:?}", e))?;
800
801 let now = std::time::SystemTime::now()
802 .duration_since(std::time::UNIX_EPOCH)
803 .context("System time error")?
804 .as_secs();
805
806 Ok(totp.check(code, now))
807 }
808
809 pub fn has_totp(&mut self) -> bool {
811 self.get_secret(Self::TOTP_SECRET_KEY, true)
812 .ok()
813 .flatten()
814 .is_some()
815 }
816
817 pub fn remove_totp(&mut self) -> Result<()> {
819 if self.has_totp() {
820 self.delete_secret(Self::TOTP_SECRET_KEY)?;
821 }
822 Ok(())
823 }
824
825 pub fn clear_cache(&mut self) {}
828
829 const BROWSER_STORE_KEY: &'static str = "__rustyclaw_browser_store";
833
834 pub fn load_browser_store(&mut self) -> Result<super::types::BrowserStore> {
836 match self.get_secret(Self::BROWSER_STORE_KEY, true)? {
837 Some(json) => {
838 let mut store: super::types::BrowserStore =
839 serde_json::from_str(&json).context("Corrupted browser store")?;
840 store.purge_expired();
842 Ok(store)
843 }
844 None => Ok(super::types::BrowserStore::new()),
845 }
846 }
847
848 pub fn save_browser_store(&mut self, store: &super::types::BrowserStore) -> Result<()> {
850 let json = serde_json::to_string(store).context("Failed to serialize browser store")?;
851 self.store_secret(Self::BROWSER_STORE_KEY, &json)
852 }
853
854 pub fn get_cookies_for_domain(
860 &mut self,
861 domain: &str,
862 path: &str,
863 user_approved: bool,
864 ) -> Result<Vec<super::types::Cookie>> {
865 if !self.agent_access_enabled && !user_approved {
866 anyhow::bail!("Access denied: agent access to browser cookies requires approval");
867 }
868
869 let store = self.load_browser_store()?;
870 Ok(store
871 .get_cookies(domain, path)
872 .into_iter()
873 .cloned()
874 .collect())
875 }
876
877 pub fn set_cookie(&mut self, cookie: super::types::Cookie, user_approved: bool) -> Result<()> {
879 if !self.agent_access_enabled && !user_approved {
880 anyhow::bail!("Access denied: agent access to browser cookies requires approval");
881 }
882
883 let mut store = self.load_browser_store()?;
884 store.set_cookie(cookie);
885 self.save_browser_store(&store)
886 }
887
888 pub fn remove_cookie(
890 &mut self,
891 domain: &str,
892 name: &str,
893 path: &str,
894 user_approved: bool,
895 ) -> Result<()> {
896 if !self.agent_access_enabled && !user_approved {
897 anyhow::bail!("Access denied: agent access to browser cookies requires approval");
898 }
899
900 let mut store = self.load_browser_store()?;
901 store.remove_cookie(domain, name, path);
902 self.save_browser_store(&store)
903 }
904
905 pub fn clear_domain_cookies(&mut self, domain: &str, user_approved: bool) -> Result<()> {
907 if !self.agent_access_enabled && !user_approved {
908 anyhow::bail!("Access denied: agent access to browser cookies requires approval");
909 }
910
911 let mut store = self.load_browser_store()?;
912 store.clear_cookies(domain);
913 self.save_browser_store(&store)
914 }
915
916 pub fn cookie_header_for_request(
921 &mut self,
922 domain: &str,
923 path: &str,
924 is_secure: bool,
925 user_approved: bool,
926 ) -> Result<Option<String>> {
927 if !self.agent_access_enabled && !user_approved {
928 return Ok(None);
929 }
930
931 let store = self.load_browser_store()?;
932 Ok(store.cookie_header(domain, path, is_secure))
933 }
934
935 pub fn store_cookies_from_response(
940 &mut self,
941 response_domain: &str,
942 set_cookie_headers: &[String],
943 user_approved: bool,
944 ) -> Result<usize> {
945 if !self.agent_access_enabled && !user_approved {
946 return Ok(0);
947 }
948
949 let mut store = self.load_browser_store()?;
950 let mut count = 0;
951
952 for header in set_cookie_headers {
953 if let Some(cookie) = Self::parse_set_cookie(header, response_domain) {
954 if Self::is_valid_cookie_domain(&cookie.domain, response_domain) {
956 store.set_cookie(cookie);
957 count += 1;
958 }
959 }
960 }
961
962 if count > 0 {
963 self.save_browser_store(&store)?;
964 }
965
966 Ok(count)
967 }
968
969 fn parse_set_cookie(header: &str, default_domain: &str) -> Option<super::types::Cookie> {
971 let parts: Vec<&str> = header.split(';').collect();
972 if parts.is_empty() {
973 return None;
974 }
975
976 let name_value: Vec<&str> = parts[0].splitn(2, '=').collect();
978 if name_value.len() != 2 {
979 return None;
980 }
981
982 let name = name_value[0].trim().to_string();
983 let value = name_value[1].trim().to_string();
984
985 if name.is_empty() {
986 return None;
987 }
988
989 let mut cookie = super::types::Cookie::new(name, value, default_domain);
990
991 for part in parts.iter().skip(1) {
993 let attr: Vec<&str> = part.splitn(2, '=').collect();
994 let attr_name = attr[0].trim().to_lowercase();
995 let attr_value = attr.get(1).map(|v| v.trim()).unwrap_or("");
996
997 match attr_name.as_str() {
998 "domain" => {
999 let domain = attr_value.to_lowercase();
1000 cookie.domain = if domain.starts_with('.') {
1002 domain
1003 } else {
1004 format!(".{}", domain)
1005 };
1006 }
1007 "path" => {
1008 cookie.path = attr_value.to_string();
1009 }
1010 "expires" => {
1011 if let Ok(ts) = httpdate::parse_http_date(attr_value) {
1014 if let Ok(duration) = ts.duration_since(std::time::UNIX_EPOCH) {
1015 cookie.expires = Some(duration.as_secs() as i64);
1016 }
1017 }
1018 }
1019 "max-age" => {
1020 if let Ok(secs) = attr_value.parse::<i64>() {
1021 let now = std::time::SystemTime::now()
1022 .duration_since(std::time::UNIX_EPOCH)
1023 .map(|d| d.as_secs() as i64)
1024 .unwrap_or(0);
1025 cookie.expires = Some(now + secs);
1026 }
1027 }
1028 "secure" => {
1029 cookie.secure = true;
1030 }
1031 "httponly" => {
1032 cookie.http_only = true;
1033 }
1034 "samesite" => {
1035 cookie.same_site = attr_value.to_lowercase();
1036 }
1037 _ => {}
1038 }
1039 }
1040
1041 Some(cookie)
1042 }
1043
1044 fn is_valid_cookie_domain(cookie_domain: &str, response_domain: &str) -> bool {
1047 let cookie_domain = cookie_domain.to_lowercase();
1048 let response_domain = response_domain.to_lowercase();
1049
1050 let cookie_base = cookie_domain.trim_start_matches('.');
1052 let response_base = response_domain.trim_start_matches('.');
1053
1054 if cookie_base == response_base {
1056 return true;
1057 }
1058
1059 if response_base.ends_with(&format!(".{}", cookie_base)) {
1062 return true;
1063 }
1064
1065 false
1066 }
1067
1068 pub fn list_cookie_domains(&mut self) -> Result<Vec<String>> {
1070 let store = self.load_browser_store()?;
1071 Ok(store.cookie_domains().into_iter().cloned().collect())
1072 }
1073
1074 pub fn storage_get(
1078 &mut self,
1079 origin: &str,
1080 key: &str,
1081 user_approved: bool,
1082 ) -> Result<Option<String>> {
1083 if !self.agent_access_enabled && !user_approved {
1084 anyhow::bail!("Access denied: agent access to web storage requires approval");
1085 }
1086
1087 let store = self.load_browser_store()?;
1088 Ok(store.storage(origin).and_then(|s| s.get(key).cloned()))
1089 }
1090
1091 pub fn storage_set(
1093 &mut self,
1094 origin: &str,
1095 key: &str,
1096 value: &str,
1097 user_approved: bool,
1098 ) -> Result<()> {
1099 if !self.agent_access_enabled && !user_approved {
1100 anyhow::bail!("Access denied: agent access to web storage requires approval");
1101 }
1102
1103 let mut store = self.load_browser_store()?;
1104 store.storage_mut(origin).set(key, value);
1105 self.save_browser_store(&store)
1106 }
1107
1108 pub fn storage_remove(&mut self, origin: &str, key: &str, user_approved: bool) -> Result<()> {
1110 if !self.agent_access_enabled && !user_approved {
1111 anyhow::bail!("Access denied: agent access to web storage requires approval");
1112 }
1113
1114 let mut store = self.load_browser_store()?;
1115 store.storage_mut(origin).remove(key);
1116 self.save_browser_store(&store)
1117 }
1118
1119 pub fn storage_clear(&mut self, origin: &str, user_approved: bool) -> Result<()> {
1121 if !self.agent_access_enabled && !user_approved {
1122 anyhow::bail!("Access denied: agent access to web storage requires approval");
1123 }
1124
1125 let mut store = self.load_browser_store()?;
1126 store.clear_storage(origin);
1127 self.save_browser_store(&store)
1128 }
1129
1130 pub fn list_storage_origins(&mut self) -> Result<Vec<String>> {
1132 let store = self.load_browser_store()?;
1133 Ok(store.storage_origins().into_iter().cloned().collect())
1134 }
1135
1136 pub fn storage_keys(&mut self, origin: &str, user_approved: bool) -> Result<Vec<String>> {
1138 if !self.agent_access_enabled && !user_approved {
1139 anyhow::bail!("Access denied: agent access to web storage requires approval");
1140 }
1141
1142 let store = self.load_browser_store()?;
1143 Ok(store
1144 .storage(origin)
1145 .map(|s| s.keys().cloned().collect())
1146 .unwrap_or_default())
1147 }
1148}