vtc-service 0.5.0

Service for Verifiable Trust Communities
Documentation
use std::future::Future;
use std::pin::Pin;

use crate::error::AppError;
use tracing::debug;

pub struct KeyringSecretStore {
    service: String,
    user: String,
}

impl KeyringSecretStore {
    pub fn new(service: impl Into<String>, user: impl Into<String>) -> Self {
        Self {
            service: service.into(),
            user: user.into(),
        }
    }
}

impl super::SecretStore for KeyringSecretStore {
    fn get(&self) -> Pin<Box<dyn Future<Output = Result<Option<Vec<u8>>, AppError>> + Send + '_>> {
        let service = self.service.clone();
        let user = self.user.clone();
        Box::pin(async move {
            tokio::task::spawn_blocking(move || {
                let entry = keyring_core::Entry::new(&service, &user).map_err(|e| {
                    AppError::SecretStore(format!("failed to create keyring entry: {e}"))
                })?;
                match entry.get_password() {
                    Ok(hex_secret) => {
                        let bytes = hex::decode(&hex_secret).map_err(|e| {
                            AppError::SecretStore(format!("failed to decode secret: {e}"))
                        })?;
                        debug!("secret loaded from keyring");
                        Ok(Some(bytes))
                    }
                    Err(keyring_core::Error::NoEntry) => {
                        debug!("no secret found in keyring");
                        Ok(None)
                    }
                    Err(e) => Err(AppError::SecretStore(format!("failed to read secret: {e}"))),
                }
            })
            .await
            .map_err(|e| AppError::Internal(format!("blocking task panicked: {e}")))?
        })
    }

    fn set(
        &self,
        secret: &[u8],
    ) -> Pin<Box<dyn Future<Output = Result<(), AppError>> + Send + '_>> {
        let service = self.service.clone();
        let user = self.user.clone();
        let hex_secret = hex::encode(secret);
        Box::pin(async move {
            tokio::task::spawn_blocking(move || {
                let entry = keyring_core::Entry::new(&service, &user).map_err(|e| {
                    AppError::SecretStore(format!("failed to create keyring entry: {e}"))
                })?;
                entry
                    .set_password(&hex_secret)
                    .map_err(|e| AppError::SecretStore(format!("failed to store secret: {e}")))?;
                debug!("secret stored in keyring");
                Ok(())
            })
            .await
            .map_err(|e| AppError::Internal(format!("blocking task panicked: {e}")))?
        })
    }

    fn delete(&self) -> Pin<Box<dyn Future<Output = Result<(), AppError>> + Send + '_>> {
        let service = self.service.clone();
        let user = self.user.clone();
        Box::pin(async move {
            tokio::task::spawn_blocking(move || {
                let entry = keyring_core::Entry::new(&service, &user).map_err(|e| {
                    AppError::SecretStore(format!("failed to create keyring entry: {e}"))
                })?;
                match entry.delete_credential() {
                    Ok(()) => {
                        debug!("secret deleted from keyring");
                        Ok(())
                    }
                    Err(keyring_core::Error::NoEntry) => Ok(()),
                    Err(e) => Err(AppError::SecretStore(format!(
                        "failed to delete secret from keyring: {e}"
                    ))),
                }
            })
            .await
            .map_err(|e| AppError::Internal(format!("blocking task panicked: {e}")))?
        })
    }
}