use anyhow::{anyhow, Context, Result};
use rand_core::CryptoRngCore;
use traits::EntryStorage;
pub(crate) use self::traits::EntryOrigin;
use self::{
auth::{Auth, AuthError},
traits::Storage,
};
use crate::{
form::{AuthForm, EntryForm, EntryOrForm, SubspaceForm, TimestampForm},
interest::{CapSelector, UserSelector},
proto::{
data_model::{AuthorisedEntry, Entry, PayloadDigest},
keys::{NamespaceId, NamespaceKind, NamespaceSecretKey, UserId},
},
store::traits::SecretStorage,
util::time::system_time_now,
};
pub(crate) mod auth;
pub mod memory;
pub mod persistent;
pub mod traits;
pub(crate) mod willow_store_glue;
#[derive(Debug, Clone)]
pub(crate) struct Store<S: Storage> {
storage: S,
auth: Auth<S>,
}
impl<S: Storage> Store<S> {
pub fn new(storage: S) -> Self {
Self {
auth: Auth::new(storage.secrets().clone(), storage.caps().clone()),
storage,
}
}
pub fn entries(&self) -> &S::Entries {
self.storage.entries()
}
pub fn secrets(&self) -> &S::Secrets {
self.storage.secrets()
}
pub fn payloads(&self) -> &iroh_blobs::api::Store {
self.storage.payloads()
}
pub fn auth(&self) -> &Auth<S> {
&self.auth
}
pub async fn insert_entry(
&self,
entry: EntryOrForm,
auth: AuthForm,
) -> Result<(AuthorisedEntry, bool)> {
let user_id = auth.user_id();
let entry = match entry {
EntryOrForm::Entry(entry) => Ok(entry),
EntryOrForm::Form(form) => self.form_to_entry(form, user_id).await,
}?;
let capability = match auth {
AuthForm::Exact(cap) => cap,
AuthForm::Any(user_id) => {
let selector = CapSelector::for_entry(&entry, UserSelector::Exact(user_id));
self.auth()
.get_write_cap(&selector)?
.ok_or_else(|| anyhow!("no write capability available"))?
}
};
let secret_key = self
.secrets()
.get_user(&user_id)?
.context("Missing user keypair")?;
let token = capability.authorisation_token(&entry, secret_key)?;
let authorised_entry = AuthorisedEntry::new_unchecked(entry, token);
let inserted = self
.entries()
.ingest_entry(&authorised_entry, EntryOrigin::Local)?;
Ok((authorised_entry, inserted))
}
pub fn create_namespace(
&self,
rng: &mut impl CryptoRngCore,
kind: NamespaceKind,
owner: UserId,
) -> Result<NamespaceId, AuthError> {
let namespace_secret = NamespaceSecretKey::generate(rng, kind);
let namespace_id = namespace_secret.id();
self.secrets().insert_namespace(namespace_secret)?;
self.auth().create_full_caps(namespace_id, owner)?;
Ok(namespace_id)
}
async fn form_to_entry(
&self,
form: EntryForm,
user_id: UserId, ) -> anyhow::Result<Entry> {
let timestamp = match form.timestamp {
TimestampForm::Now => system_time_now(),
TimestampForm::Exact(timestamp) => timestamp,
};
let subspace_id = match form.subspace_id {
SubspaceForm::User => user_id,
SubspaceForm::Exact(subspace) => subspace,
};
let (payload_digest, payload_length) = form.payload.submit(self.payloads()).await?;
let entry = Entry::new(
form.namespace_id,
subspace_id,
form.path,
timestamp,
payload_length,
PayloadDigest(payload_digest),
);
Ok(entry)
}
}