1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
use std::str::FromStr;

use crate::native::workspace::Workspace;
use anyhow::{anyhow, Result};
use cid::Cid;
use noosphere::{
    key::KeyStorage,
    sphere::{SphereContext, SphereContextBuilder},
};
use noosphere_core::{authority::Authorization, data::Did};

use ucan::crypto::KeyMaterial;

pub async fn sphere_create(owner_key: &str, workspace: &Workspace) -> Result<()> {
    if workspace.sphere_directory().exists() {
        return Err(anyhow!(
            "A sphere is already initialized in {:?}",
            workspace.root_directory()
        ));
    }

    let sphere_context_artifacts = SphereContextBuilder::default()
        .create_sphere()
        .at_storage_path(workspace.root_directory())
        .reading_keys_from(workspace.key_storage().clone())
        .using_key(owner_key)
        .build()
        .await?;

    let mnemonic = sphere_context_artifacts.require_mnemonic()?.to_string();
    let sphere_context: SphereContext<_, _> = sphere_context_artifacts.into();
    let sphere_identity = sphere_context.identity();

    println!(
        r#"A new sphere has been created in {:?}
Its identity is {}
Your key {:?} is considered its owner
The owner of a sphere can authorize other keys to write to it

IMPORTANT: Write down the following sequence of words...

{}

...and keep it somewhere safe!
You will be asked to enter them if you ever need to transfer ownership of the sphere to a different key."#,
        workspace.root_directory(),
        sphere_identity,
        owner_key,
        mnemonic
    );

    Ok(())
}

pub async fn sphere_join(
    local_key: &str,
    authorization: Option<String>,
    sphere_identity: &Did,
    workspace: &Workspace,
) -> Result<()> {
    if workspace.sphere_directory().exists() {
        return Err(anyhow!(
            "A sphere is already initialized in {:?}",
            workspace.root_directory()
        ));
    }

    println!("Joining sphere {}...", sphere_identity);

    let did = {
        let local_key = workspace.key_storage().require_key(local_key).await?;
        local_key.get_did().await?
    };

    let cid_string = match authorization {
        None => {
            println!(
                r#"In order to join the sphere, another client must authorize your local key
This is the local key's ID; share it with an authorized client:

  {0}

Hint: if the authorized client is also using the "orb" CLI, you can use this command from the existing workspace to authorize the new key:

  orb auth add {0}
  
Once authorized, you will get a code.
Type or paste the code here and press enter:"#,
                did
            );

            let mut cid_string = String::new();

            std::io::stdin().read_line(&mut cid_string)?;

            cid_string
        }
        Some(cid_string) => cid_string,
    };

    let cid = Cid::from_str(cid_string.trim())
        .map_err(|_| anyhow!("Could not parse the authorization identity as a CID"))?;

    SphereContextBuilder::default()
        .join_sphere(sphere_identity)
        .at_storage_path(workspace.root_directory())
        .reading_keys_from(workspace.key_storage().clone())
        .using_key(local_key)
        .authorized_by(Some(&Authorization::Cid(cid)))
        .build()
        .await?;

    // TODO(#103): Recovery path if the auth needs to change for some reason

    println!(
        r#"The authorization has been saved.
Make sure that you have configured the gateway's URL:

  orb config set gateway-url <URL>
  
And then you should be able to sync:

  orb sync
  
Happy pondering!"#
    );

    Ok(())
}