use std::pin::Pin;
use credo::{
ClaimBody, Credential, CredentialSource, CredentialSourceHash, PermissionKind, ScopeID,
SimpleCredentialSource,
};
use futures::{Future, FutureExt, StreamExt, future};
use jmbl::Input;
use litl::Val;
use mofo::Mofo;
use ridl::asymm_encr::RecipientID;
use tracing::debug;
use crate::{
conventions::{
claim_for_introduction, claim_to_name_doc, CREDENTIALS_DOC_NAME, INCLUDE_LOG_STR,
NAMED_DOC_PREFIX, PROFILE_DOC_NAME, READ_CONTENT,
},
team::{Team, TeamID},
Doc, Jazz,
};
#[derive(Clone)]
pub struct AccountCredentialSource {
account_id: TeamID,
account_credential: Credential,
credentials_doc: future::Shared<Pin<Box<dyn Future<Output = Doc>>>>
}
impl CredentialSource for AccountCredentialSource {
fn add_credential<'a>(
&'a self,
scope_id: ScopeID,
credential: Credential,
) -> std::pin::Pin<Box<dyn futures::Future<Output = ()> + 'a>> {
if scope_id == self.account_id.0 {
panic!("Can't add credential for account document");
}
async move {
let credentials_doc = self.credentials_doc.clone().await;
let credentials_doc_view = credentials_doc.managed_jmbl.start_writing();
credentials_doc_view
.get_root()
.if_map_mut()
.expect("Expected credentials doc to be a map")
.insert(scope_id.to_string(), litl::to_val(&credential).unwrap());
credentials_doc
.managed_jmbl
.finish_writing(credentials_doc_view);
}
.boxed_local()
}
fn credentials_for<'a>(
&'a self,
scope_id: &'a ScopeID,
) -> Pin<Box<dyn Future<Output = Vec<Credential>> + 'a>> {
async move {
let credentials_doc = self.credentials_doc.clone().await;
if scope_id == &self.account_id.0 {
vec![self.account_credential.clone()]
} else if scope_id == &credentials_doc.scope.id() {
vec![]
} else {
let mut doc_updates = credentials_doc
.managed_jmbl
.updates(format!("credentials_for_{}", scope_id));
while let Some((root, _)) = doc_updates.next().await {
if let Some(credential) = root.if_map().ok().and_then(|root_map| {
root_map
.get(&scope_id.to_string())
.if_plain()
.ok()
.and_then(|credential_litl| {
litl::from_val(credential_litl.clone()).ok()
})
}) {
return vec![credential];
}
}
unreachable!("Account doc listener closed before credential was found");
}
}
.boxed_local()
}
fn id_hash(&self) -> CredentialSourceHash {
CredentialSourceHash::from_hashable(self.account_id)
}
fn clone_ref(&self) -> Box<dyn CredentialSource> {
Box::new(self.clone())
}
}
impl Jazz {
pub async fn new_with_account(
name: String,
background: Mofo,
account_id: TeamID,
account_credential: Credential,
) -> Self {
let jazz = Jazz::new(name, background);
let root_credential_source = Box::new(SimpleCredentialSource::new());
root_credential_source
.add_credential(account_id.0.clone(), account_credential.clone())
.await;
let account_team =
jazz.get_team_with_credential_source(&account_id, root_credential_source.clone());
let jazz_for_credentials_doc = jazz.clone();
let credentials_doc = async move {
let credentials_doc_id = account_team
.resolve_named_document(CREDENTIALS_DOC_NAME)
.await;
jazz_for_credentials_doc
.load_document_with_credential_source(credentials_doc_id, root_credential_source)
.await
}.boxed_local();
let account_credential_source = AccountCredentialSource {
account_id,
account_credential,
credentials_doc: credentials_doc.shared(),
};
jazz.0.borrow_mut().main_credential_source = Box::new(account_credential_source);
jazz
}
pub async fn new_with_create_account(
name: String,
background: Mofo,
) -> (Jazz, TeamID, Credential) {
let jazz = Jazz::new(name, background);
let root_credential_source = Box::new(SimpleCredentialSource::new());
let account_credential = Credential::new_random();
let account_team = jazz
.create_team_for_with_credential_source(
account_credential.clone(),
vec![
PermissionKind::MakeStatement {
path_prefix: NAMED_DOC_PREFIX.to_string(),
},
PermissionKind::DelegateInfinitely {
delegated: Box::new(PermissionKind::MakeStatement {
path_prefix: NAMED_DOC_PREFIX.to_string(),
}),
},
],
root_credential_source,
)
.await;
let credentials_doc = jazz
.create_document(&account_team, Input::CollabMap(vec![]))
.await;
account_team
.scope
.make_claim(claim_to_name_doc(
credentials_doc.id(),
CREDENTIALS_DOC_NAME,
))
.await
.unwrap();
let account_credential_source = AccountCredentialSource {
account_id: account_team.id(),
account_credential: account_credential.clone(),
credentials_doc: future::ready(credentials_doc).boxed_local().shared(),
};
jazz.0.borrow_mut().main_credential_source = Box::new(account_credential_source);
(jazz, account_team.id(), account_credential)
}
pub async fn create_profile(&self, in_account: &TeamID, content: Input) -> Doc {
let profile_team = self.create_team().await;
debug!("Creating profile: {:?}", content);
let profile_doc = self.create_document(&profile_team, content).await;
profile_doc
.scope
.make_claim(ClaimBody::AddSharedSecretRecipient {
secret_kind: READ_CONTENT.to_string(),
recipient: RecipientID::everyone(),
})
.await
.unwrap();
profile_team
.scope
.make_claim(claim_to_name_doc(profile_doc.id(), PROFILE_DOC_NAME))
.await
.unwrap();
let account = self.get_team(in_account);
let name_profile_claim = claim_to_name_doc(profile_doc.id(), PROFILE_DOC_NAME);
account
.scope.await_credential_for(&name_profile_claim).await;
account
.scope
.make_claim(name_profile_claim)
.await
.unwrap();
profile_doc
}
pub async fn get_account_profile(&self, account_id: &TeamID) -> Doc {
let account = self.get_team(account_id);
let profile_doc_id = account.resolve_named_document(PROFILE_DOC_NAME).await;
self.load_document(profile_doc_id).await
}
pub async fn create_team_and_introduce_as(&self, account_id: &TeamID) -> Team {
let team = self.create_team().await;
self.introduce_in_as(&team, account_id).await;
team
}
pub async fn introduce_in_as(&self, team: &Team, account_id: &TeamID) {
let profile = self.get_account_profile(account_id).await;
let profile_credential = profile
.scope
.credential_for(&ClaimBody::Statement {
path: INCLUDE_LOG_STR.to_string(),
value: Val::null(),
})
.await
.expect("Expected to have credential for profile");
team.scope
.make_claim(claim_for_introduction(
profile.id(),
&*profile_credential.for_making_claims,
))
.await
.unwrap();
}
}