dipper 0.5.3

An out-of-the-box modular dependency injection web application framework.
Documentation
use std::{env, ops::Deref, sync::Arc, time::Duration};

use argon2::{
    Argon2,
    password_hash::{PasswordHasher, SaltString},
};
use myotel::warn;
use sha2::{Digest, Sha224};
use tokio::{sync::RwLock, task::JoinHandle, time::sleep};

/// Generate keys synchronously.
pub fn gen_secret_key(master_secret_key: &str, salt: &str) -> anyhow::Result<String> {
    let mut hasher = Sha224::new();
    hasher.update(salt);
    let salt = hasher.finalize();
    let salt = SaltString::encode_b64(salt.as_slice()).map_err(|err| anyhow::anyhow!("{err}"))?;
    // Argon2 with default params (Argon2id v19)
    let argon2 = Argon2::default();
    // Hash password to PHC string ($argon2id$v=19$...)
    Ok(argon2
        .hash_password(master_secret_key.as_bytes(), &salt)
        .map_err(|err| anyhow::anyhow!("{err}"))?
        .to_string())
}

/// Generate a fixed sub-key from the master key.
/// When the refresh_interval is greater than 0, perform timed updates.
#[derive(Debug)]
pub struct SecretKeyCache<P> {
    args: Arc<SecretArgs>,
    secret_key: Arc<RwLock<String>>,
    provider: Arc<P>,
    refresh_interval: Option<Duration>,
    join_handle: Option<JoinHandle<()>>,
}

impl<P: SecretArgsProvide> SecretKeyCache<P> {
    /// new SecretKeyCache
    pub fn new(provider: P, refresh_interval: Option<Duration>) -> anyhow::Result<Self> {
        let args = provider.secret_key_args()?;
        let secret_key = Arc::new(RwLock::new(Self::new_secret_key(&args)?));
        let mut s = Self {
            args: Arc::new(args),
            secret_key,
            provider: Arc::new(provider),
            refresh_interval,
            join_handle: None,
        };
        if let Some(refresh_interval) = refresh_interval {
            if !refresh_interval.is_zero() {
                s.join_handle.replace(s.refresh());
            }
        }
        Ok(s)
    }
    /// Obtain the latest key.
    pub async fn obtain_secret_key(&self) -> String {
        self.secret_key.read().await.clone()
    }
    fn new_secret_key(
        SecretArgs {
            master_secret_key,
            salt,
        }: &SecretArgs,
    ) -> anyhow::Result<String> {
        gen_secret_key(master_secret_key, salt)
    }
    fn refresh(&self) -> JoinHandle<()> {
        let args = self.args.clone();
        let secret_key = self.secret_key.clone();
        let provide = self.provider.clone();
        let refresh_interval = self.refresh_interval.unwrap();
        tokio::spawn(async move {
            loop {
                sleep(refresh_interval).await;
                match provide.secret_key_args() {
                    Ok(new_args) if new_args == *args => {
                        continue;
                    }
                    Err(err) => {
                        warn!("Failed to generate secret key args, error={err:?}");
                        continue;
                    }
                    Ok(new_args) => match Self::new_secret_key(&new_args) {
                        Ok(new_secret_key) => *secret_key.write().await = new_secret_key,
                        Err(err) => {
                            warn!("Failed to generate secret key, error={err:?}")
                        }
                    },
                }
            }
        })
    }
}

impl<P> Drop for SecretKeyCache<P> {
    fn drop(&mut self) {
        if let Some(join_handle) = &self.join_handle {
            join_handle.abort();
        }
    }
}

#[derive(Debug, Clone, PartialEq)]
pub struct SecretArgs {
    pub master_secret_key: String,
    pub salt: String,
}

pub trait SecretArgsProvide: Sync + Send + 'static {
    fn secret_key_args(&self) -> anyhow::Result<SecretArgs>;
}

impl<T: SecretArgsProvide> SecretArgsProvide for Arc<T> {
    fn secret_key_args(&self) -> anyhow::Result<SecretArgs> {
        self.deref().secret_key_args()
    }
}

impl SecretArgsProvide for SecretArgs {
    fn secret_key_args(&self) -> anyhow::Result<SecretArgs> {
        Ok(self.clone())
    }
}

pub const ENV_DIPPER_SECRET_KEY: &str = "DIPPER_SECRET_KEY";
pub const ENV_DIPPER_SECRET_SATL: &str = "DIPPER_SECRET_SATL";

#[derive(Debug, Clone)]
pub struct EnvSecretArgsProvide(());

impl EnvSecretArgsProvide {
    pub fn new() -> anyhow::Result<Self> {
        let s = Self(());
        let _ = s.secret_key_args()?;
        Ok(s)
    }
}

impl SecretArgsProvide for EnvSecretArgsProvide {
    fn secret_key_args(&self) -> anyhow::Result<SecretArgs> {
        Ok(SecretArgs {
            master_secret_key: env::var(ENV_DIPPER_SECRET_KEY)?,
            salt: env::var(ENV_DIPPER_SECRET_SATL)?,
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn test_gen_secret_key() {
        let secret_key = gen_secret_key("123456", "x-mod").unwrap();
        println!("{secret_key}");
    }
}