act_credentials/backend/
file.rs1use std::collections::BTreeMap;
2use std::path::{Path, PathBuf};
3
4use serde::{Deserialize, Serialize};
5
6use crate::index::{Index, write_private};
7use crate::record::{SecretInfo, SecretRecord};
8use crate::store::{CredentialStore, StoreError};
9
10pub struct FileStore {
14 root: PathBuf,
15}
16
17#[derive(Debug, Default, Serialize, Deserialize)]
18struct Secrets {
19 entries: BTreeMap<String, BTreeMap<String, SecretRecord>>,
21}
22
23pub fn secrets_path(root: &Path) -> PathBuf {
30 root.join("secrets.json")
31}
32
33impl FileStore {
34 pub fn new(root: PathBuf) -> Self {
35 Self { root }
36 }
37
38 fn secrets_path(&self) -> PathBuf {
39 secrets_path(&self.root)
40 }
41
42 fn load(&self) -> Result<Secrets, StoreError> {
43 let p = self.secrets_path();
44 if !p.exists() {
45 return Ok(Secrets::default());
46 }
47 let text = std::fs::read_to_string(p)?;
48 serde_json::from_str(&text).map_err(|e| StoreError::Encoding(e.to_string()))
49 }
50
51 fn save(&self, s: &Secrets) -> Result<(), StoreError> {
52 let text =
53 serde_json::to_string_pretty(s).map_err(|e| StoreError::Encoding(e.to_string()))?;
54 write_private(&self.secrets_path(), text.as_bytes())
55 }
56}
57
58impl CredentialStore for FileStore {
59 fn get(&self, component: &str, key: &str) -> Result<Option<SecretRecord>, StoreError> {
60 Ok(self
61 .load()?
62 .entries
63 .get(component)
64 .and_then(|m| m.get(key))
65 .cloned())
66 }
67
68 fn put(&self, component: &str, key: &str, rec: &SecretRecord) -> Result<(), StoreError> {
69 let mut s = self.load()?;
70 s.entries
71 .entry(component.to_string())
72 .or_default()
73 .insert(key.to_string(), rec.clone());
74 self.save(&s)?;
75
76 let mut idx = Index::load(&self.root)?;
77 idx.upsert(component, rec.info(key));
78 idx.save(&self.root)
79 }
80
81 fn erase(&self, component: &str, key: &str) -> Result<(), StoreError> {
82 let mut s = self.load()?;
83 if let Some(m) = s.entries.get_mut(component) {
84 m.remove(key);
85 if m.is_empty() {
86 s.entries.remove(component);
87 }
88 }
89 self.save(&s)?;
90
91 let mut idx = Index::load(&self.root)?;
92 idx.remove(component, key);
93 idx.save(&self.root)
94 }
95
96 fn list(&self, component: Option<&str>) -> Result<Vec<SecretInfo>, StoreError> {
97 Ok(Index::load(&self.root)?.list(component))
98 }
99
100 fn components(&self) -> Result<Vec<String>, StoreError> {
101 Ok(Index::load(&self.root)?.entries.into_keys().collect())
102 }
103
104 fn update(
105 &self,
106 component: &str,
107 key: &str,
108 mutate: &mut dyn FnMut(&mut SecretRecord),
109 ) -> Result<Option<SecretRecord>, StoreError> {
110 let _guard = ExclusiveLock::acquire(&self.root)?;
114
115 let mut s = self.load()?;
116 let Some(rec) = s.entries.get_mut(component).and_then(|m| m.get_mut(key)) else {
117 return Ok(None);
118 };
119 mutate(rec);
120 let updated = rec.clone();
121 self.save(&s)?;
122
123 let mut idx = Index::load(&self.root)?;
124 idx.upsert(component, updated.info(key));
125 idx.save(&self.root)?;
126 Ok(Some(updated))
127 }
128}
129
130struct ExclusiveLock(std::fs::File);
135
136impl ExclusiveLock {
137 fn acquire(root: &Path) -> Result<Self, StoreError> {
138 let path = root.join("secrets.lock");
139 crate::index::create_dir_private(root)?;
140 let file = std::fs::OpenOptions::new()
141 .create(true)
142 .truncate(false)
143 .write(true)
144 .open(&path)?;
145 <std::fs::File as fs4::FileExt>::lock(&file)?;
149 Ok(Self(file))
150 }
151}
152
153impl Drop for ExclusiveLock {
154 fn drop(&mut self) {
155 let _ = <std::fs::File as fs4::FileExt>::unlock(&self.0);
156 }
157}