keyring_store.rs (5631B)
1 use enostr::{Pubkey, SecretKey}; 2 use keyring::Entry; 3 4 use crate::{Error, Result}; 5 6 const KEYRING_SERVICE_NAME: &str = "com.damus.notedeck"; 7 8 type BackendResult<T> = std::result::Result<T, keyring::Error>; 9 10 #[derive(Clone, Debug)] 11 enum KeyringBackendType { 12 OS(OsKeyringBackend), 13 #[cfg(test)] 14 Memory(MemoryKeyringBackend), 15 } 16 17 impl KeyringBackendType { 18 pub fn set(&self, service: &str, account: &str, secret: &str) -> BackendResult<()> { 19 match self { 20 KeyringBackendType::OS(os_keyring_backend) => { 21 os_keyring_backend.set(service, account, secret) 22 } 23 #[cfg(test)] 24 KeyringBackendType::Memory(mem) => mem.set(service, account, secret), 25 } 26 } 27 28 pub fn get(&self, service: &str, account: &str) -> BackendResult<Option<String>> { 29 match self { 30 KeyringBackendType::OS(os_keyring_backend) => os_keyring_backend.get(service, account), 31 #[cfg(test)] 32 KeyringBackendType::Memory(memory_keyring_backend) => { 33 memory_keyring_backend.get(service, account) 34 } 35 } 36 } 37 38 pub fn delete(&self, service: &str, account: &str) -> BackendResult<()> { 39 match self { 40 KeyringBackendType::OS(os_keyring_backend) => { 41 os_keyring_backend.delete(service, account) 42 } 43 #[cfg(test)] 44 KeyringBackendType::Memory(memory_keyring_backend) => { 45 memory_keyring_backend.delete(service, account) 46 } 47 } 48 } 49 } 50 51 #[derive(Clone, Debug)] 52 struct OsKeyringBackend; 53 54 impl OsKeyringBackend { 55 fn set(&self, service: &str, account: &str, secret: &str) -> BackendResult<()> { 56 let entry = Entry::new(service, account)?; 57 entry.set_password(secret) 58 } 59 60 fn get(&self, service: &str, account: &str) -> BackendResult<Option<String>> { 61 let entry = Entry::new(service, account)?; 62 63 match entry.get_password() { 64 Ok(secret) => Ok(Some(secret)), 65 Err(keyring::Error::NoEntry) => Ok(None), 66 Err(err) => Err(err), 67 } 68 } 69 70 fn delete(&self, service: &str, account: &str) -> BackendResult<()> { 71 let entry = Entry::new(service, account)?; 72 73 match entry.delete_credential() { 74 Ok(_) => Ok(()), 75 Err(keyring::Error::NoEntry) => Ok(()), 76 Err(err) => Err(err), 77 } 78 } 79 } 80 81 #[derive(Clone, Debug)] 82 pub struct KeyringStore { 83 backend: KeyringBackendType, 84 } 85 86 impl KeyringStore { 87 #[cfg(test)] 88 pub fn in_memory() -> Self { 89 Self { 90 backend: KeyringBackendType::Memory(MemoryKeyringBackend::default()), 91 } 92 } 93 94 pub fn store_secret(&self, pubkey: &Pubkey, secret: &SecretKey) -> Result<()> { 95 let res = self 96 .backend 97 .set( 98 KEYRING_SERVICE_NAME, 99 &Self::account_id(pubkey), 100 &secret.to_secret_hex(), 101 ) 102 .map_err(Error::from); 103 104 tracing::trace!("Store secret result: {res:?}"); 105 106 res 107 } 108 109 pub fn get_secret(&self, pubkey: &Pubkey) -> Result<Option<SecretKey>> { 110 let maybe_secret = self 111 .backend 112 .get(KEYRING_SERVICE_NAME, &Self::account_id(pubkey)) 113 .map_err(Error::from); 114 115 let secret_hex = match maybe_secret { 116 Ok(m_secret) => { 117 let Some(secret) = m_secret else { 118 tracing::trace!("Keyring gave us empty secret for {pubkey}"); 119 return Ok(None); 120 }; 121 tracing::trace!("Received an actual secret for {pubkey} successfully"); 122 secret 123 } 124 Err(e) => { 125 tracing::trace!("Failed to retrieve secret for {pubkey}: {e}"); 126 return Err(e); 127 } 128 }; 129 130 let secret_key = SecretKey::from_hex(secret_hex).map_err(|err| { 131 Error::Generic(format!( 132 "invalid secret key from keyring for {}: {err}", 133 Self::account_id(pubkey) 134 )) 135 })?; 136 137 Ok(Some(secret_key)) 138 } 139 140 pub fn remove_secret(&self, pubkey: &Pubkey) -> Result<()> { 141 self.backend 142 .delete(KEYRING_SERVICE_NAME, &Self::account_id(pubkey)) 143 .map_err(Error::from) 144 } 145 146 fn account_id(pubkey: &Pubkey) -> String { 147 pubkey.hex() 148 } 149 } 150 151 impl Default for KeyringStore { 152 fn default() -> Self { 153 Self { 154 backend: KeyringBackendType::OS(OsKeyringBackend), 155 } 156 } 157 } 158 159 #[cfg(test)] 160 #[derive(Clone, Default, Debug)] 161 struct MemoryKeyringBackend { 162 // RwLock to not make the KeyringBackendType api mutable... it's only for testing so it's ok 163 entries: std::sync::Arc<std::sync::RwLock<std::collections::HashMap<(String, String), String>>>, 164 } 165 166 #[cfg(test)] 167 impl MemoryKeyringBackend { 168 fn set(&self, service: &str, account: &str, secret: &str) -> BackendResult<()> { 169 self.entries 170 .write() 171 .unwrap() 172 .insert((service.to_owned(), account.to_owned()), secret.to_owned()); 173 Ok(()) 174 } 175 176 fn get(&self, service: &str, account: &str) -> BackendResult<Option<String>> { 177 Ok(self 178 .entries 179 .read() 180 .unwrap() 181 .get(&(service.to_owned(), account.to_owned())) 182 .cloned()) 183 } 184 185 fn delete(&self, service: &str, account: &str) -> BackendResult<()> { 186 self.entries 187 .write() 188 .unwrap() 189 .remove(&(service.to_owned(), account.to_owned())); 190 Ok(()) 191 } 192 }