gestura_core_security/
storage.rs1use std::collections::HashMap;
7use thiserror::Error;
8
9#[derive(Debug, Error)]
11pub enum SecureStorageError {
12 #[error("Storage error: {0}")]
14 Storage(String),
15
16 #[error("Key not found: {0}")]
18 NotFound(String),
19
20 #[error("Lock poisoned")]
22 LockPoisoned,
23
24 #[error("I/O error: {0}")]
26 Io(#[from] std::io::Error),
27}
28
29impl From<SecureStorageError> for gestura_core_foundation::AppError {
30 fn from(err: SecureStorageError) -> Self {
31 gestura_core_foundation::AppError::Io(std::io::Error::other(err.to_string()))
32 }
33}
34
35#[async_trait::async_trait]
40pub trait SecureStorage: Send + Sync {
41 async fn store_secret(&self, key: &str, value: &str) -> Result<(), SecureStorageError>;
43
44 async fn get_secret(&self, key: &str) -> Result<Option<String>, SecureStorageError>;
46
47 async fn delete_secret(&self, key: &str) -> Result<(), SecureStorageError>;
49
50 async fn has_secret(&self, key: &str) -> Result<bool, SecureStorageError> {
52 Ok(self.get_secret(key).await?.is_some())
53 }
54}
55
56pub struct MockSecureStorage {
61 data: std::sync::RwLock<HashMap<String, String>>,
62}
63
64impl Default for MockSecureStorage {
65 fn default() -> Self {
66 Self {
67 data: std::sync::RwLock::new(HashMap::new()),
68 }
69 }
70}
71
72impl MockSecureStorage {
73 pub fn new() -> Self {
75 Self::default()
76 }
77
78 pub fn with_secrets(secrets: HashMap<String, String>) -> Self {
80 Self {
81 data: std::sync::RwLock::new(secrets),
82 }
83 }
84}
85
86#[async_trait::async_trait]
87impl SecureStorage for MockSecureStorage {
88 async fn store_secret(&self, key: &str, value: &str) -> Result<(), SecureStorageError> {
89 let mut data = self
90 .data
91 .write()
92 .map_err(|_| SecureStorageError::LockPoisoned)?;
93 data.insert(key.to_string(), value.to_string());
94 Ok(())
95 }
96
97 async fn get_secret(&self, key: &str) -> Result<Option<String>, SecureStorageError> {
98 let data = self
99 .data
100 .read()
101 .map_err(|_| SecureStorageError::LockPoisoned)?;
102 Ok(data.get(key).cloned())
103 }
104
105 async fn delete_secret(&self, key: &str) -> Result<(), SecureStorageError> {
106 let mut data = self
107 .data
108 .write()
109 .map_err(|_| SecureStorageError::LockPoisoned)?;
110 data.remove(key);
111 Ok(())
112 }
113}
114
115#[cfg(feature = "security")]
122pub struct KeychainStorage;
123
124#[cfg(feature = "security")]
125#[async_trait::async_trait]
126impl SecureStorage for KeychainStorage {
127 async fn store_secret(&self, key: &str, value: &str) -> Result<(), SecureStorageError> {
128 let entry = keyring::Entry::new("gestura", key)
129 .map_err(|e| SecureStorageError::Storage(e.to_string()))?;
130 entry
131 .set_password(value)
132 .map_err(|e| SecureStorageError::Storage(e.to_string()))?;
133 Ok(())
134 }
135
136 async fn get_secret(&self, key: &str) -> Result<Option<String>, SecureStorageError> {
137 let entry = keyring::Entry::new("gestura", key)
138 .map_err(|e| SecureStorageError::Storage(e.to_string()))?;
139 match entry.get_password() {
140 Ok(password) => Ok(Some(password)),
141 Err(keyring::Error::NoEntry) => Ok(None),
142 Err(e) => Err(SecureStorageError::Storage(e.to_string())),
143 }
144 }
145
146 async fn delete_secret(&self, key: &str) -> Result<(), SecureStorageError> {
147 let entry = keyring::Entry::new("gestura", key)
148 .map_err(|e| SecureStorageError::Storage(e.to_string()))?;
149 match entry.delete_password() {
150 Ok(()) => Ok(()),
151 Err(keyring::Error::NoEntry) => Ok(()),
152 Err(e) => Err(SecureStorageError::Storage(e.to_string())),
153 }
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160
161 #[tokio::test]
162 async fn test_mock_storage_store_and_get() {
163 let storage = MockSecureStorage::new();
164 storage
165 .store_secret("test_key", "test_value")
166 .await
167 .unwrap();
168 let value = storage.get_secret("test_key").await.unwrap();
169 assert_eq!(value, Some("test_value".to_string()));
170 }
171
172 #[tokio::test]
173 async fn test_mock_storage_delete() {
174 let storage = MockSecureStorage::new();
175 storage.store_secret("key", "value").await.unwrap();
176 storage.delete_secret("key").await.unwrap();
177 let value = storage.get_secret("key").await.unwrap();
178 assert!(value.is_none());
179 }
180
181 #[tokio::test]
182 async fn test_mock_storage_has_secret() {
183 let storage = MockSecureStorage::new();
184 assert!(!storage.has_secret("key").await.unwrap());
185 storage.store_secret("key", "value").await.unwrap();
186 assert!(storage.has_secret("key").await.unwrap());
187 }
188}