sos-database 0.17.5

Database backend for the Save Our Secrets SDK
Documentation
//! Write vault changes to a database.
use crate::{
    entity::{FolderEntity, FolderRecord, SecretRecord, SecretRow},
    Error,
};
use async_sqlite::Client;
use async_trait::async_trait;
use sos_core::{
    commit::CommitHash,
    crypto::AeadPack,
    encode,
    events::{ReadEvent, WriteEvent},
    SecretId, VaultCommit, VaultEntry, VaultFlags, VaultId,
};
use sos_vault::{EncryptedEntry, Summary, Vault};
use std::borrow::Cow;

/// Write changes to a vault in the database.
pub struct VaultDatabaseWriter<E>
where
    E: std::error::Error
        + std::fmt::Debug
        + From<sos_core::Error>
        + From<sos_vault::Error>
        + From<Error>
        + Send
        + Sync
        + 'static,
{
    client: Client,
    folder_id: VaultId,
    marker: std::marker::PhantomData<E>,
}

impl<E> VaultDatabaseWriter<E>
where
    E: std::error::Error
        + std::fmt::Debug
        + From<sos_core::Error>
        + From<sos_vault::Error>
        + From<Error>
        + Send
        + Sync
        + 'static,
{
    /// Create a new vault database writer.
    pub fn new(client: Client, folder_id: VaultId) -> Self {
        Self {
            client,
            folder_id,
            marker: std::marker::PhantomData,
        }
    }
}

#[async_trait]
impl<E> EncryptedEntry for VaultDatabaseWriter<E>
where
    E: std::error::Error
        + std::fmt::Debug
        + From<sos_core::Error>
        + From<sos_vault::Error>
        + From<Error>
        + Send
        + Sync
        + 'static,
{
    type Error = E;

    async fn summary(&self) -> Result<Summary, Self::Error> {
        let folder_id = self.folder_id;
        let row = self
            .client
            .conn(move |conn| {
                let folder = FolderEntity::new(&conn);
                let row = folder.find_optional(&folder_id)?;
                Ok(row)
            })
            .await
            .map_err(Error::from)?;
        let row = row.ok_or(Error::DatabaseFolderNotFound(folder_id))?;
        let record = FolderRecord::from_row(row).await?;
        Ok(record.summary)
    }

    async fn vault_name(&self) -> Result<Cow<'_, str>, Self::Error> {
        let summary = self.summary().await?;
        Ok(Cow::Owned(summary.name().to_string()))
    }

    async fn set_vault_name(
        &mut self,
        name: String,
    ) -> Result<WriteEvent, Self::Error> {
        let folder_id = self.folder_id;
        let folder_name = name.clone();
        self.client
            .conn_and_then(move |conn| {
                let folder = FolderEntity::new(&conn);
                folder.update_name(&folder_id, &folder_name)
            })
            .await?;
        Ok(WriteEvent::SetVaultName(name))
    }

    async fn set_vault_flags(
        &mut self,
        flags: VaultFlags,
    ) -> Result<WriteEvent, Self::Error> {
        let folder_id = self.folder_id;
        let folder_flags = flags.clone();
        self.client
            .conn_and_then(move |conn| {
                let folder = FolderEntity::new(&conn);
                folder.update_flags(&folder_id, &folder_flags)
            })
            .await?;
        Ok(WriteEvent::SetVaultFlags(flags))
    }

    async fn set_vault_meta(
        &mut self,
        meta_data: AeadPack,
    ) -> Result<WriteEvent, Self::Error> {
        let folder_id = self.folder_id;
        let folder_meta = encode(&meta_data).await?;
        self.client
            .conn_and_then(move |conn| {
                let folder = FolderEntity::new(&conn);
                folder.update_meta(&folder_id, folder_meta.as_slice())
            })
            .await?;
        Ok(WriteEvent::SetVaultMeta(meta_data))
    }

    async fn create_secret(
        &mut self,
        commit: CommitHash,
        secret: VaultEntry,
    ) -> Result<WriteEvent, Self::Error> {
        self.insert_secret(SecretId::new_v4(), commit, secret).await
    }

    async fn insert_secret(
        &mut self,
        secret_id: SecretId,
        commit: CommitHash,
        secret: VaultEntry,
    ) -> Result<WriteEvent, Self::Error> {
        let folder_id = self.folder_id;
        let secret_row = SecretRow::new(&secret_id, &commit, &secret).await?;
        self.client
            .conn(move |conn| {
                let folder = FolderEntity::new(&conn);
                folder.insert_secret(&folder_id, &secret_row)?;
                Ok(())
            })
            .await
            .map_err(Error::from)?;
        Ok(WriteEvent::CreateSecret(
            secret_id,
            VaultCommit(commit, secret),
        ))
    }

    async fn read_secret<'a>(
        &'a self,
        secret_id: &SecretId,
    ) -> Result<Option<(Cow<'a, VaultCommit>, ReadEvent)>, Self::Error> {
        let folder_id = self.folder_id;
        let folder_secret_id = *secret_id;
        let secret_row = self
            .client
            .conn(move |conn| {
                let folder = FolderEntity::new(&conn);
                folder.find_secret(&folder_id, &folder_secret_id)
            })
            .await
            .map_err(Error::from)?;

        let event = ReadEvent::ReadSecret(*secret_id);
        if let Some(row) = secret_row {
            let record = SecretRecord::from_row(row).await?;
            Ok(Some((Cow::Owned(record.commit), event)))
        } else {
            Ok(None)
        }
    }

    async fn update_secret(
        &mut self,
        secret_id: &SecretId,
        commit: CommitHash,
        secret: VaultEntry,
    ) -> Result<Option<WriteEvent>, Self::Error> {
        let folder_id = self.folder_id;
        let secret_row = SecretRow::new(secret_id, &commit, &secret).await?;
        let updated = self
            .client
            .conn_and_then(move |conn| {
                let folder = FolderEntity::new(&conn);
                folder.update_secret(&folder_id, &secret_row)
            })
            .await?;
        Ok(updated.then_some(WriteEvent::UpdateSecret(
            *secret_id,
            VaultCommit(commit, secret),
        )))
    }

    async fn delete_secret(
        &mut self,
        secret_id: &SecretId,
    ) -> Result<Option<WriteEvent>, Self::Error> {
        let folder_id = self.folder_id;
        let folder_secret_id = *secret_id;
        let deleted = self
            .client
            .conn(move |conn| {
                let folder = FolderEntity::new(&conn);
                folder.delete_secret(&folder_id, &folder_secret_id)
            })
            .await
            .map_err(Error::from)?;
        Ok(deleted.then_some(WriteEvent::DeleteSecret(*secret_id)))
    }

    async fn replace_vault(
        &mut self,
        vault: &Vault,
    ) -> Result<(), Self::Error> {
        Ok(FolderEntity::replace_all_secrets(
            self.client.clone(),
            &self.folder_id,
            vault,
        )
        .await?)
    }
}