noosphere_cli/native/commands/sphere/
mod.rs

1//! Concrete implementations of the various subcommands of the sphere command
2
3mod auth;
4mod config;
5mod follow;
6mod history;
7mod render;
8mod save;
9mod status;
10mod sync;
11
12pub use auth::*;
13pub use config::*;
14pub use follow::*;
15pub use history::*;
16pub use render::*;
17pub use save::*;
18pub use status::*;
19pub use sync::*;
20
21use std::{str::FromStr, sync::Arc};
22
23use crate::native::{paths::SpherePaths, workspace::Workspace};
24use anyhow::{anyhow, Result};
25use cid::Cid;
26use noosphere::{
27    key::KeyStorage,
28    sphere::{SphereContextBuilder, SphereContextBuilderArtifacts},
29};
30use noosphere_core::{authority::Authorization, data::Did};
31use noosphere_core::{
32    context::{HasMutableSphereContext, SphereContext, SphereSync},
33    data::Mnemonic,
34};
35
36use tokio::sync::Mutex;
37use ucan::crypto::KeyMaterial;
38use url::Url;
39
40/// Create a sphere, assigning authority to modify it to the given key
41/// (specified by nickname)
42pub async fn sphere_create(owner_key: &str, workspace: &mut Workspace) -> Result<(Did, Mnemonic)> {
43    workspace.ensure_sphere_uninitialized()?;
44
45    let sphere_paths = SpherePaths::initialize(workspace.working_directory()).await?;
46
47    let sphere_context_artifacts = SphereContextBuilder::default()
48        .create_sphere()
49        .at_storage_path(sphere_paths.root())
50        .reading_keys_from(workspace.key_storage().clone())
51        .using_key(owner_key)
52        .build()
53        .await?;
54
55    let mnemonic = sphere_context_artifacts.require_mnemonic()?.to_string();
56    let sphere_context: SphereContext<_> = sphere_context_artifacts.into();
57    let sphere_identity = sphere_context.identity();
58
59    info!(
60        r#"A new sphere has been created in {:?}
61Its identity is {}
62Your key {:?} is considered its owner
63The owner of a sphere can authorize other keys to write to it
64
65IMPORTANT: Write down the following sequence of words...
66
67{}
68
69...and keep it somewhere safe!
70You will be asked to enter them if you ever need to transfer ownership of the sphere to a different key."#,
71        sphere_paths.root(),
72        sphere_identity,
73        owner_key,
74        mnemonic
75    );
76
77    workspace.initialize(sphere_paths)?;
78
79    Ok((sphere_identity.clone(), mnemonic.into()))
80}
81
82/// Join an existing sphere
83pub async fn sphere_join(
84    local_key: &str,
85    authorization: Option<String>,
86    sphere_identity: &Did,
87    gateway_url: &Url,
88    render_depth: Option<u32>,
89    workspace: &mut Workspace,
90) -> Result<()> {
91    workspace.ensure_sphere_uninitialized()?;
92    info!("Joining sphere {sphere_identity}...");
93
94    let did = {
95        let local_key = workspace.key_storage().require_key(local_key).await?;
96        local_key.get_did().await?
97    };
98
99    let cid_string = match authorization {
100        None => {
101            info!(
102                r#"In order to join the sphere, another client must authorize your local key
103This is the local key's ID; share it with an authorized client:
104
105  {did}
106
107Hint: if the authorized client is also using the "orb" CLI, you can use this command from the existing workspace to authorize the new key:
108
109  orb auth add {did}
110  
111Once authorized, you will get a code.
112Type or paste the code here and press enter:"#
113            );
114
115            let mut cid_string = String::new();
116
117            std::io::stdin().read_line(&mut cid_string)?;
118
119            cid_string
120        }
121        Some(cid_string) => cid_string,
122    };
123
124    let cid = Cid::from_str(cid_string.trim())
125        .map_err(|_| anyhow!("Could not parse the authorization identity as a CID"))?;
126
127    let sphere_paths = SpherePaths::initialize(workspace.working_directory()).await?;
128
129    {
130        let mut sphere_context = Arc::new(Mutex::new(
131            match SphereContextBuilder::default()
132                .join_sphere(sphere_identity)
133                .at_storage_path(sphere_paths.root())
134                .reading_keys_from(workspace.key_storage().clone())
135                .using_key(local_key)
136                .authorized_by(Some(&Authorization::Cid(cid)))
137                .build()
138                .await?
139            {
140                SphereContextBuilderArtifacts::SphereCreated { context, .. } => context,
141                SphereContextBuilderArtifacts::SphereOpened(context) => context,
142            },
143        ));
144
145        sphere_context
146            .sphere_context_mut()
147            .await?
148            .configure_gateway_url(Some(gateway_url))
149            .await?;
150        sphere_context.sync().await?;
151    }
152
153    workspace.initialize(sphere_paths)?;
154    workspace.render(render_depth, true).await?;
155
156    // TODO(#103): Recovery path if the auth needs to change for some reason
157
158    info!(
159        r#"The authorization has been saved. You should be able to sync:
160
161  orb sphere sync
162  
163Happy pondering!"#
164    );
165
166    Ok(())
167}