lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

vault.rs (4391B)


      1 #[cfg(feature = "os-keyring")]
      2 use alloc::format;
      3 use alloc::string::String;
      4 
      5 use crate::error::RadrootsSecretVaultAccessError;
      6 
      7 pub trait RadrootsSecretVault: Send + Sync {
      8     fn store_secret(&self, slot: &str, secret: &str) -> Result<(), RadrootsSecretVaultAccessError>;
      9 
     10     fn load_secret(&self, slot: &str) -> Result<Option<String>, RadrootsSecretVaultAccessError>;
     11 
     12     fn remove_secret(&self, slot: &str) -> Result<(), RadrootsSecretVaultAccessError>;
     13 }
     14 
     15 #[cfg(feature = "memory-vault")]
     16 #[derive(Debug, Clone, Default)]
     17 pub struct RadrootsSecretVaultMemory {
     18     entries: std::sync::Arc<std::sync::RwLock<std::collections::HashMap<String, String>>>,
     19 }
     20 
     21 #[cfg(feature = "memory-vault")]
     22 impl RadrootsSecretVaultMemory {
     23     #[must_use]
     24     pub fn new() -> Self {
     25         Self::default()
     26     }
     27 }
     28 
     29 #[cfg(feature = "memory-vault")]
     30 impl RadrootsSecretVault for RadrootsSecretVaultMemory {
     31     fn store_secret(&self, slot: &str, secret: &str) -> Result<(), RadrootsSecretVaultAccessError> {
     32         let mut guard = self
     33             .entries
     34             .write()
     35             .map_err(|_| RadrootsSecretVaultAccessError::Backend("memory vault poisoned".into()))?;
     36         guard.insert(String::from(slot), String::from(secret));
     37         Ok(())
     38     }
     39 
     40     fn load_secret(&self, slot: &str) -> Result<Option<String>, RadrootsSecretVaultAccessError> {
     41         let guard = self
     42             .entries
     43             .read()
     44             .map_err(|_| RadrootsSecretVaultAccessError::Backend("memory vault poisoned".into()))?;
     45         Ok(guard.get(slot).cloned())
     46     }
     47 
     48     fn remove_secret(&self, slot: &str) -> Result<(), RadrootsSecretVaultAccessError> {
     49         let mut guard = self
     50             .entries
     51             .write()
     52             .map_err(|_| RadrootsSecretVaultAccessError::Backend("memory vault poisoned".into()))?;
     53         guard.remove(slot);
     54         Ok(())
     55     }
     56 }
     57 
     58 #[cfg(feature = "os-keyring")]
     59 #[derive(Debug, Clone)]
     60 pub struct RadrootsSecretVaultOsKeyring {
     61     service_name: String,
     62 }
     63 
     64 #[cfg(feature = "os-keyring")]
     65 impl RadrootsSecretVaultOsKeyring {
     66     #[must_use]
     67     pub fn new(service_name: impl Into<String>) -> Self {
     68         Self {
     69             service_name: service_name.into(),
     70         }
     71     }
     72 }
     73 
     74 #[cfg(feature = "os-keyring")]
     75 impl Default for RadrootsSecretVaultOsKeyring {
     76     fn default() -> Self {
     77         Self::new("org.radroots.secret-vault")
     78     }
     79 }
     80 
     81 #[cfg(feature = "os-keyring")]
     82 impl RadrootsSecretVault for RadrootsSecretVaultOsKeyring {
     83     fn store_secret(&self, slot: &str, secret: &str) -> Result<(), RadrootsSecretVaultAccessError> {
     84         let entry = keyring::Entry::new(self.service_name.as_str(), slot)
     85             .map_err(|source| RadrootsSecretVaultAccessError::Backend(format!("{source}")))?;
     86         entry
     87             .set_password(secret)
     88             .map_err(|source| RadrootsSecretVaultAccessError::Backend(format!("{source}")))
     89     }
     90 
     91     fn load_secret(&self, slot: &str) -> Result<Option<String>, RadrootsSecretVaultAccessError> {
     92         let entry = keyring::Entry::new(self.service_name.as_str(), slot)
     93             .map_err(|source| RadrootsSecretVaultAccessError::Backend(format!("{source}")))?;
     94         match entry.get_password() {
     95             Ok(secret) => Ok(Some(secret)),
     96             Err(keyring::Error::NoEntry) => Ok(None),
     97             Err(source) => Err(RadrootsSecretVaultAccessError::Backend(format!("{source}"))),
     98         }
     99     }
    100 
    101     fn remove_secret(&self, slot: &str) -> Result<(), RadrootsSecretVaultAccessError> {
    102         let entry = keyring::Entry::new(self.service_name.as_str(), slot)
    103             .map_err(|source| RadrootsSecretVaultAccessError::Backend(format!("{source}")))?;
    104         match entry.delete_credential() {
    105             Ok(_) | Err(keyring::Error::NoEntry) => Ok(()),
    106             Err(source) => Err(RadrootsSecretVaultAccessError::Backend(format!("{source}"))),
    107         }
    108     }
    109 }
    110 
    111 #[cfg(all(test, feature = "memory-vault"))]
    112 mod tests {
    113     use super::*;
    114 
    115     #[test]
    116     fn memory_vault_round_trip() {
    117         let vault = RadrootsSecretVaultMemory::new();
    118         vault.store_secret("alice", "abc123").expect("store");
    119         let loaded = vault.load_secret("alice").expect("load");
    120         assert_eq!(loaded.as_deref(), Some("abc123"));
    121         vault.remove_secret("alice").expect("remove");
    122         let loaded = vault.load_secret("alice").expect("load");
    123         assert!(loaded.is_none());
    124     }
    125 }