tauri_plugin_keyring_store/
store.rs1use std::collections::HashSet;
21use std::sync::{Arc, Mutex};
22
23use keyring_core::Entry;
24use sha2::{Digest, Sha256};
25
26use crate::backend::{ensure_init, map_keyring_err};
27use crate::error::{Error, Result};
28use crate::models::BytesDto;
29
30fn digest16(data: &[u8]) -> String {
31 let mut h = Sha256::new();
32 h.update(data);
33 let out = h.finalize();
34 hex::encode(&out[..8])
35}
36
37#[derive(Default, Clone)]
39pub struct SessionRegistry(pub Arc<Mutex<HashSet<String>>>);
40
41impl SessionRegistry {
42 pub fn insert(&self, path: String) {
44 self.0.lock().expect("session mutex poisoned").insert(path);
45 }
46
47 pub fn remove(&self, path: &str) -> bool {
49 self.0.lock().expect("session mutex poisoned").remove(path)
50 }
51
52 pub fn contains(&self, path: &str) -> bool {
54 self.0
55 .lock()
56 .expect("session mutex poisoned")
57 .contains(path)
58 }
59}
60
61#[derive(Debug, Clone)]
63pub struct KeyringStore {
64 service: String,
65}
66
67impl KeyringStore {
68 pub fn new(service: impl Into<String>) -> Self {
70 Self {
71 service: service.into(),
72 }
73 }
74
75 pub fn service(&self) -> &str {
77 &self.service
78 }
79
80 fn entry(&self, account: &str) -> Result<Entry> {
81 ensure_init().map_err(Error::Init)?;
82 Entry::new(&self.service, account).map_err(|e| Error::Keyring(e.to_string()))
83 }
84
85 pub fn set_password(&self, account: &str, password: &str) -> Result<()> {
87 let entry = self.entry(account)?;
88 entry.set_password(password).map_err(map_keyring_err)
89 }
90
91 pub fn set_bytes(&self, account: &str, value: &[u8]) -> Result<()> {
93 let encoded = base64::Engine::encode(&base64::engine::general_purpose::STANDARD, value);
94 self.set_password(account, &encoded)
95 }
96
97 pub fn get_password(&self, account: &str) -> Result<Option<String>> {
99 let entry = self.entry(account)?;
100 match entry.get_password() {
101 Ok(p) => Ok(Some(p)),
102 Err(e) => {
103 if matches!(&e, keyring_core::error::Error::NoEntry) {
104 Ok(None)
105 } else {
106 Err(map_keyring_err(e))
107 }
108 }
109 }
110 }
111
112 pub fn get_bytes(&self, account: &str) -> Result<Option<Vec<u8>>> {
114 match self.get_password(account)? {
115 None => Ok(None),
116 Some(s) => {
117 let raw =
118 base64::Engine::decode(&base64::engine::general_purpose::STANDARD, s.trim())
119 .map_err(|e| Error::Encoding(e.to_string()))?;
120 Ok(Some(raw))
121 }
122 }
123 }
124
125 pub fn delete(&self, account: &str) -> Result<()> {
127 let entry = self.entry(account)?;
128 match entry.delete_credential() {
129 Ok(()) => Ok(()),
130 Err(e) => {
131 if matches!(&e, keyring_core::error::Error::NoEntry) {
132 Ok(())
133 } else {
134 Err(map_keyring_err(e))
135 }
136 }
137 }
138 }
139
140 pub fn exists_nonempty(&self, account: &str) -> Result<bool> {
142 Ok(self
143 .get_password(account)?
144 .map(|v| !v.trim().is_empty())
145 .unwrap_or(false))
146 }
147
148 pub fn account_raw(&self, snapshot_path: &str, client: &BytesDto, suffix: &str) -> String {
161 let sd = digest16(snapshot_path.as_bytes());
162 let cd = digest16(client.as_ref());
163 let xd = digest16(suffix.as_bytes());
164 format!("kp:v1:{sd}:{cd}:x:{xd}")
165 }
166
167 pub fn account_store_key(
169 &self,
170 snapshot_path: &str,
171 client: &BytesDto,
172 store_key: &str,
173 ) -> String {
174 let sd = digest16(snapshot_path.as_bytes());
175 let cd = digest16(client.as_ref());
176 let kd = digest16(store_key.as_bytes());
177 format!("kp:v1:{sd}:{cd}:st:{kd}")
178 }
179
180 pub fn account_vault_record(
182 &self,
183 snapshot_path: &str,
184 client: &BytesDto,
185 vault: &BytesDto,
186 record_path: &BytesDto,
187 ) -> String {
188 let sd = digest16(snapshot_path.as_bytes());
189 let cd = digest16(client.as_ref());
190 let vd = digest16(vault.as_ref());
191 let rd = digest16(record_path.as_ref());
192 format!("kp:v1:{sd}:{cd}:v:{vd}:{rd}")
193 }
194}