notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

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 }