linux_key_storage.rs (6818B)
1 #![cfg(target_os = "linux")] 2 3 use enostr::{Keypair, SerializableKeypair}; 4 use std::fs; 5 use std::io::Write; 6 use std::path::PathBuf; 7 use std::{env, fs::File}; 8 9 use crate::key_storage::{KeyStorage, KeyStorageError, KeyStorageResponse}; 10 use tracing::debug; 11 12 enum LinuxKeyStorageType { 13 BasicFileStorage, 14 // TODO(kernelkind): could use the secret service api, and maybe even allow password manager integration via a settings menu 15 } 16 17 pub struct LinuxKeyStorage {} 18 19 // TODO(kernelkind): read from settings instead of hard-coding 20 static USE_MECHANISM: LinuxKeyStorageType = LinuxKeyStorageType::BasicFileStorage; 21 22 impl LinuxKeyStorage { 23 pub fn new() -> Self { 24 Self {} 25 } 26 } 27 28 impl KeyStorage for LinuxKeyStorage { 29 fn get_keys(&self) -> KeyStorageResponse<Vec<enostr::Keypair>> { 30 match USE_MECHANISM { 31 LinuxKeyStorageType::BasicFileStorage => BasicFileStorage::new().get_keys(), 32 } 33 } 34 35 fn add_key(&self, key: &enostr::Keypair) -> KeyStorageResponse<()> { 36 match USE_MECHANISM { 37 LinuxKeyStorageType::BasicFileStorage => BasicFileStorage::new().add_key(key), 38 } 39 } 40 41 fn remove_key(&self, key: &enostr::Keypair) -> KeyStorageResponse<()> { 42 match USE_MECHANISM { 43 LinuxKeyStorageType::BasicFileStorage => BasicFileStorage::new().remove_key(key), 44 } 45 } 46 } 47 48 struct BasicFileStorage { 49 credential_dir_name: String, 50 } 51 52 impl BasicFileStorage { 53 pub fn new() -> Self { 54 Self { 55 credential_dir_name: ".credentials".to_string(), 56 } 57 } 58 59 fn mock() -> Self { 60 Self { 61 credential_dir_name: ".credentials_test".to_string(), 62 } 63 } 64 65 fn get_cred_dirpath(&self) -> Result<PathBuf, KeyStorageError> { 66 let home_dir = env::var("HOME") 67 .map_err(|_| KeyStorageError::OSError("HOME env variable not set".to_string()))?; 68 let home_path = std::path::PathBuf::from(home_dir); 69 let project_path_str = "notedeck"; 70 71 let config_path = { 72 if let Some(xdg_config_str) = env::var_os("XDG_CONFIG_HOME") { 73 let xdg_path = PathBuf::from(xdg_config_str); 74 let xdg_path_config = if xdg_path.is_absolute() { 75 xdg_path 76 } else { 77 home_path.join(".config") 78 }; 79 xdg_path_config.join(project_path_str) 80 } else { 81 home_path.join(format!(".{}", project_path_str)) 82 } 83 } 84 .join(self.credential_dir_name.clone()); 85 86 std::fs::create_dir_all(&config_path).map_err(|_| { 87 KeyStorageError::OSError(format!( 88 "could not create config path: {}", 89 config_path.display() 90 )) 91 })?; 92 93 Ok(config_path) 94 } 95 96 fn add_key_internal(&self, key: &Keypair) -> Result<(), KeyStorageError> { 97 let mut file_path = self.get_cred_dirpath()?; 98 file_path.push(format!("{}", &key.pubkey)); 99 100 let mut file = File::create(file_path) 101 .map_err(|_| KeyStorageError::Addition("could not create or open file".to_string()))?; 102 103 let json_str = serde_json::to_string(&SerializableKeypair::from_keypair(key, "", 7)) 104 .map_err(|e| KeyStorageError::Addition(e.to_string()))?; 105 file.write_all(json_str.as_bytes()).map_err(|_| { 106 KeyStorageError::Addition("could not write keypair to file".to_string()) 107 })?; 108 109 Ok(()) 110 } 111 112 fn get_keys_internal(&self) -> Result<Vec<Keypair>, KeyStorageError> { 113 let file_path = self.get_cred_dirpath()?; 114 let mut keys: Vec<Keypair> = Vec::new(); 115 116 if !file_path.is_dir() { 117 return Err(KeyStorageError::Retrieval( 118 "path is not a directory".to_string(), 119 )); 120 } 121 122 let dir = fs::read_dir(file_path).map_err(|_| { 123 KeyStorageError::Retrieval("problem accessing credentials directory".to_string()) 124 })?; 125 126 for entry in dir { 127 let entry = entry.map_err(|_| { 128 KeyStorageError::Retrieval("problem accessing crediential file".to_string()) 129 })?; 130 131 let path = entry.path(); 132 133 if path.is_file() { 134 if let Some(path_str) = path.to_str() { 135 debug!("key path {}", path_str); 136 let json_string = fs::read_to_string(path_str).map_err(|e| { 137 KeyStorageError::OSError(format!("File reading problem: {}", e)) 138 })?; 139 let key: SerializableKeypair = 140 serde_json::from_str(&json_string).map_err(|e| { 141 KeyStorageError::OSError(format!( 142 "Deserialization problem: {}", 143 (e.to_string().as_str()) 144 )) 145 })?; 146 keys.push(key.to_keypair("")) 147 } 148 } 149 } 150 151 Ok(keys) 152 } 153 154 fn remove_key_internal(&self, key: &Keypair) -> Result<(), KeyStorageError> { 155 let path = self.get_cred_dirpath()?; 156 157 let filepath = path.join(key.pubkey.to_string()); 158 159 if filepath.exists() && filepath.is_file() { 160 fs::remove_file(&filepath) 161 .map_err(|e| KeyStorageError::OSError(format!("failed to remove file: {}", e)))?; 162 } 163 164 Ok(()) 165 } 166 } 167 168 impl KeyStorage for BasicFileStorage { 169 fn get_keys(&self) -> crate::key_storage::KeyStorageResponse<Vec<enostr::Keypair>> { 170 KeyStorageResponse::ReceivedResult(self.get_keys_internal()) 171 } 172 173 fn add_key(&self, key: &enostr::Keypair) -> crate::key_storage::KeyStorageResponse<()> { 174 KeyStorageResponse::ReceivedResult(self.add_key_internal(key)) 175 } 176 177 fn remove_key(&self, key: &enostr::Keypair) -> crate::key_storage::KeyStorageResponse<()> { 178 KeyStorageResponse::ReceivedResult(self.remove_key_internal(key)) 179 } 180 } 181 182 mod tests { 183 use crate::key_storage::{KeyStorage, KeyStorageResponse}; 184 185 use super::BasicFileStorage; 186 187 #[test] 188 fn test_basic() { 189 let kp = enostr::FullKeypair::generate().to_keypair(); 190 let resp = BasicFileStorage::mock().add_key(&kp); 191 192 assert_eq!(resp, KeyStorageResponse::ReceivedResult(Ok(()))); 193 assert_num_storage(1); 194 195 let resp = BasicFileStorage::mock().remove_key(&kp); 196 assert_eq!(resp, KeyStorageResponse::ReceivedResult(Ok(()))); 197 assert_num_storage(0); 198 } 199 200 #[allow(dead_code)] 201 fn assert_num_storage(n: usize) { 202 let resp = BasicFileStorage::mock().get_keys(); 203 204 if let KeyStorageResponse::ReceivedResult(Ok(vec)) = resp { 205 assert_eq!(vec.len(), n); 206 return; 207 } 208 panic!(); 209 } 210 }