noosphere_cli/native/commands/sphere/
auth.rs1use std::{collections::BTreeMap, convert::TryFrom};
2
3use anyhow::{anyhow, Result};
4use cid::Cid;
5use noosphere_core::context::{
6 HasSphereContext, SphereAuthorityRead, SphereAuthorityWrite, SphereWalker,
7};
8use noosphere_core::data::{Did, Jwt, Link};
9use serde_json::{json, Value};
10use ucan::{store::UcanJwtStore, Ucan};
11
12use tokio_stream::StreamExt;
13
14use crate::native::{commands::sphere::save, workspace::Workspace};
15
16pub async fn auth_add(did: &Did, name: Option<String>, workspace: &Workspace) -> Result<Cid> {
18 workspace.ensure_sphere_initialized()?;
19
20 let mut sphere_context = workspace.sphere_context().await?;
21
22 let current_authorization = sphere_context.get_authorization(did).await?;
23
24 if let Some(authorization) = current_authorization {
25 let cid = Cid::try_from(authorization)?;
26
27 return Err(anyhow!(
28 r#"{} is already authorized to access the sphere
29Here is the identity of the authorization:
30
31 {}
32
33If you want to change it to something new, revoke the current one first:
34
35 orb auth revoke {}
36
37You will be able to add a new one after the old one is revoked"#,
38 did,
39 cid,
40 cid
41 ));
42 }
43
44 let name = match name {
45 Some(name) => name,
46 None => {
47 let random_name = witty_phrase_generator::WPGen::new()
48 .with_words(3)
49 .unwrap_or_else(|| vec!["Unnamed"])
50 .into_iter()
51 .map(String::from)
52 .collect::<Vec<String>>()
53 .join("-");
54 info!(
55 "Note: since no name was specified, the authorization will be saved with the generated name \"{random_name}\""
56 );
57 random_name
58 }
59 };
60
61 let new_authorization = sphere_context.authorize(&name, did).await?;
62 let new_authorization_cid = Cid::try_from(new_authorization)?;
63
64 save(None, workspace).await?;
65
66 info!(
67 r#"Successfully authorized {did} to access your sphere.
68
69IMPORTANT: You MUST sync to enable your gateway to recognize the authorization:
70
71 orb sync
72
73This is the authorization's identity:
74
75 {new_authorization_cid}
76
77Use this identity when joining the sphere on the other client"#
78 );
79
80 Ok(new_authorization_cid)
81}
82
83fn draw_branch(
84 mut items: Vec<Link<Jwt>>,
85 mut indentation: Vec<bool>,
86 hierarchy: &BTreeMap<Link<Jwt>, Vec<Link<Jwt>>>,
87 meta: &BTreeMap<Link<Jwt>, (String, Did, Jwt)>,
88) {
89 let prefix = indentation
90 .iter()
91 .enumerate()
92 .map(|(index, is_last)| match is_last {
93 false if index == 0 => "│",
94 false => " │ ",
95 true if index == 0 => " ",
96 true => " ",
97 })
98 .collect::<String>();
99
100 while let Some(link) = items.pop() {
101 let is_last = items.is_empty();
102
103 if let Some((name, id, _token)) = meta.get(&link) {
104 let (branch_char, trunk_char) = match is_last {
105 true => ('└', ' '),
106 false => ('├', '│'),
107 };
108
109 info!("{prefix}{}── {}", branch_char, name);
110 info!("{prefix}{} {}", trunk_char, id);
111
112 if let Some(children) = hierarchy.get(&link) {
113 info!("{prefix}{} │", trunk_char);
114 indentation.push(is_last);
115 draw_branch(children.clone(), indentation.clone(), hierarchy, meta);
116 } else {
117 info!("{prefix}{}", trunk_char);
118 }
119 }
120 }
121}
122
123pub async fn auth_list(tree: bool, as_json: bool, workspace: &Workspace) -> Result<()> {
126 let sphere_context = workspace.sphere_context().await?;
127 let sphere_identity = sphere_context.identity().await?;
128 let db = sphere_context.lock().await.db().clone();
129
130 let walker = SphereWalker::from(&sphere_context);
131
132 let authorization_stream = walker.authorization_stream();
133
134 tokio::pin!(authorization_stream);
135
136 let mut authorization_meta: BTreeMap<Link<Jwt>, (String, Did, Jwt)> = BTreeMap::default();
137 let mut max_name_length: usize = 7;
138
139 while let Some((name, identity, link)) = authorization_stream.try_next().await? {
140 let jwt = Jwt(db.require_token(&link).await?);
141 max_name_length = max_name_length.max(name.len());
142 authorization_meta.insert(link, (name, identity, jwt));
143 }
144
145 if tree {
146 let mut authorization_roots = Vec::<Link<Jwt>>::new();
147 let mut authorization_hierarchy: BTreeMap<Link<Jwt>, Vec<Link<Jwt>>> = BTreeMap::default();
148
149 for (link, (_name, _identity, jwt)) in authorization_meta.iter() {
150 let ucan = Ucan::try_from(jwt.as_str())?;
151
152 let proofs = ucan
155 .proofs()
156 .clone()
157 .unwrap_or_default()
158 .into_iter()
159 .filter_map(|cid| Cid::try_from(cid.as_str()).ok().map(Link::from))
160 .collect::<Vec<Link<Jwt>>>();
161
162 if *ucan.issuer() == sphere_identity {
163 authorization_roots.push(*link)
166 } else {
167 for proof in proofs {
168 let items = match authorization_hierarchy.get_mut(&proof) {
169 Some(items) => items,
170 None => {
171 authorization_hierarchy.insert(proof, Vec::new());
172 authorization_hierarchy.get_mut(&proof).ok_or_else(|| {
173 anyhow!(
174 "Could not access list of child authorizations for {}",
175 &proof
176 )
177 })?
178 }
179 };
180 items.push(*link);
181 }
182 }
183 }
184
185 if as_json {
186 let mut hierarchy = BTreeMap::new();
187 for (proof, children) in authorization_hierarchy {
188 hierarchy.insert(
189 proof.to_string(),
190 children
191 .iter()
192 .map(|child| child.to_string())
193 .collect::<Vec<String>>(),
194 );
195 }
196 let mut meta = BTreeMap::new();
197 for (link, (name, id, token)) in authorization_meta {
198 meta.insert(
199 link.to_string(),
200 json!({
201 "name": name,
202 "id": id,
203 "token": token
204 }),
205 );
206 }
207
208 let roots = authorization_roots
209 .into_iter()
210 .map(|root| root.to_string())
211 .collect::<Vec<String>>();
212
213 info!(
214 "{}",
215 serde_json::to_string_pretty(&json!({
216 "sphere": sphere_identity,
217 "roots": roots,
218 "hierarchy": hierarchy,
219 "meta": meta
220 }))?
221 );
222 } else {
223 info!(" ⌀ Sphere");
224 info!(" │ {sphere_identity}");
225 info!(" │");
226
227 while let Some(root) = authorization_roots.pop() {
228 let items = vec![root];
229 let indentation = vec![authorization_roots.is_empty()];
230
231 draw_branch(
232 items,
233 indentation,
234 &authorization_hierarchy,
235 &authorization_meta,
236 );
237 }
238 }
239 } else if as_json {
240 let authorizations: Vec<Value> = authorization_meta
241 .into_iter()
242 .map(|(link, (name, did, token))| {
243 json!({
244 "name": name,
245 "id": did,
246 "link": link.to_string(),
247 "token": token
248 })
249 })
250 .collect();
251 info!("{}", serde_json::to_string_pretty(&json!(authorizations))?);
252 } else {
253 info!("{:1$} AUTHORIZED KEY", "NAME", max_name_length);
254 for (_, (name, did, _)) in authorization_meta {
255 info!("{name:max_name_length$} {did}");
256 }
257 }
258
259 Ok(())
260}
261
262pub async fn auth_revoke(name: &str, workspace: &Workspace) -> Result<()> {
264 workspace.ensure_sphere_initialized()?;
265
266 let mut sphere_context = workspace.sphere_context().await?;
267 let authorizations = sphere_context.get_authorizations_by_name(name).await?;
268
269 if authorizations.is_empty() {
270 return Err(anyhow!("There is no authorization named {:?}", name));
271 }
272
273 for authorization in authorizations.iter() {
274 sphere_context.revoke_authorization(authorization).await?;
275 }
276
277 save(None, workspace).await?;
278
279 Ok(())
280}