jazz-rs 0.7.0

A framework for CRDT based, end-to-end enrypted distributed apps
Documentation
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() {
                // prevent recursion, caller should use credentials from parent team
                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();
    }
}