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