Skip to content
This repository has been archived by the owner on Sep 21, 2024. It is now read-only.

feat: Initial work refactoring noosphere-gateway to be generic over the spheres it manages. #698

Merged
merged 1 commit into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ anyhow = { version = "1" }
async-recursion = { version = "1" }
async-stream = { version = "0.3" }
axum = { version = "^0.6.18" }
base64 = { version = "^0.21" }
byteorder = { version = "~1.4" } # keep in sync with pinned libipld-* crates
bytes = { version = "^1" }
cid = { version = "0.10" }
deterministic-bloom = { version = "0.1.0" }
Expand All @@ -29,6 +31,7 @@ futures = { version = "0.3" }
futures-util = { version = "0.3" }
gloo-net = { version = "0.4" }
gloo-timers = { version = "0.3", features = ["futures"] }
headers = { version = "=0.3.8" } # Match version used by `axum`.
ignore = { version = "0.4.20" }
instant = { version = "0.1", features = ["wasm-bindgen"] }
iroh-car = { version = "^0.3.0" }
Expand Down
46 changes: 26 additions & 20 deletions rust/noosphere-cli/src/native/commands/serve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

use crate::native::workspace::Workspace;
use anyhow::Result;
use noosphere_gateway::{start_gateway, GatewayScope};
use noosphere_core::context::HasSphereContext;
use noosphere_gateway::{Gateway, SingleTenantGatewayManager};
use std::net::{IpAddr, TcpListener};
use url::Url;

Expand All @@ -19,25 +20,30 @@ pub async fn serve(
workspace.ensure_sphere_initialized()?;

let listener = TcpListener::bind((interface, port))?;

let counterpart = workspace.counterpart_identity().await?;

let identity = workspace.sphere_identity().await?;

let gateway_scope = GatewayScope {
identity,
counterpart,
};

let sphere_context = workspace.sphere_context().await?;

start_gateway(
listener,
gateway_scope,
sphere_context,
ipfs_api,
name_resolver_api,
cors_origin,
)
.await
let gateway_identity = sphere_context
.sphere_context()
.await?
.author()
.did()
.await?;
let manager = SingleTenantGatewayManager::new(sphere_context, counterpart.clone()).await?;

let gateway = Gateway::new(manager, ipfs_api, name_resolver_api, cors_origin)?;
jsantell marked this conversation as resolved.
Show resolved Hide resolved

info!(
r#"A geist is summoned to manage local sphere {}

It has bound a gateway to {:?}
It awaits updates from sphere {}..."#,
gateway_identity,
listener
.local_addr()
.expect("Unexpected missing listener address"),
counterpart
);

gateway.start(listener).await?;
Ok(())
}
28 changes: 8 additions & 20 deletions rust/noosphere-cli/src/native/helpers/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,8 @@ use noosphere::{
NoosphereContext, NoosphereContextConfiguration, NoosphereNetwork, NoosphereSecurity,
NoosphereStorage, NoosphereStorageConfig, NoosphereStoragePath,
};
use noosphere_core::{
context::HasSphereContext,
data::{Did, Mnemonic},
};
use noosphere_gateway::{start_gateway, GatewayScope};
use noosphere_core::data::{Did, Mnemonic};
use noosphere_gateway::{Gateway, SingleTenantGatewayManager};
use noosphere_ns::{helpers::NameSystemNetwork, server::start_name_system_api_server};
use tokio::{sync::Mutex, task::JoinHandle};
use url::Url;
Expand Down Expand Up @@ -56,25 +53,15 @@ async fn start_gateway_for_workspace(
))?;

let gateway_sphere_context = workspace.sphere_context().await?;

let client_sphere_identity = client_sphere_identity.clone();
let client_sphere_identity = client_sphere_identity.to_owned();
let ns_url = ns_url.clone();
let ipfs_url = ipfs_url.clone();
let manager =
SingleTenantGatewayManager::new(gateway_sphere_context, client_sphere_identity).await?;

let join_handle = tokio::spawn(async move {
start_gateway(
gateway_listener,
GatewayScope {
identity: gateway_sphere_context.identity().await.unwrap(),
counterpart: client_sphere_identity,
},
gateway_sphere_context,
ipfs_url,
ns_url,
None,
)
.await
.unwrap()
let gateway = Gateway::new(manager, ipfs_url, ns_url, None).unwrap();
gateway.start(gateway_listener).await.unwrap()
});

Ok((gateway_url, join_handle))
Expand Down Expand Up @@ -241,6 +228,7 @@ impl SpherePair {
if self.gateway_task.is_some() {
return Err(anyhow::anyhow!("Gateway already started."));
}

let (gateway_url, gateway_task) = start_gateway_for_workspace(
&self.gateway.workspace,
&self.client.identity,
Expand Down
2 changes: 1 addition & 1 deletion rust/noosphere-collections/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ cid = { workspace = true }
forest_hash_utils = "0.1.0"
serde = { workspace = true }
serde_bytes = "0.11"
byteorder = "^1.5"
byteorder = { workspace = true }
async-recursion = { workspace = true }
libipld-core = { workspace = true }
libipld-cbor = { workspace = true }
Expand Down
6 changes: 2 additions & 4 deletions rust/noosphere-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,7 @@ futures-util = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
serde_urlencoded = { workspace = true }
byteorder = "^1.5"
base64 = "0.21"
base64 = { workspace = true }
ed25519-zebra = "^3"
rand = { workspace = true }
once_cell = "^1"
Expand All @@ -57,7 +56,7 @@ reqwest = { workspace = true }
strum = { workspace = true }
strum_macros = { workspace = true }
tokio-util = { workspace = true, features = ["io"] }

headers = { workspace = true }
noosphere-common = { version = "0.1.2", path = "../noosphere-common" }
noosphere-storage = { version = "0.9.3", path = "../noosphere-storage" }
noosphere-collections = { version = "0.6.7", path = "../noosphere-collections" }
Expand All @@ -74,7 +73,6 @@ noosphere-common = { version = "0.1.2", path = "../noosphere-common", features =
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tokio = { workspace = true, features = ["full"] }
tracing-subscriber = { workspace = true }
axum = { workspace = true, features = ["headers", "macros"] }

[target.'cfg(target_arch = "wasm32")'.dependencies]
# NOTE: This is needed so that rand can be included in WASM builds
Expand Down
10 changes: 5 additions & 5 deletions rust/noosphere-core/src/api/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use crate::{
error::NoosphereError,
stream::{from_car_stream, memo_history_stream, put_block_stream, to_car_stream},
};

use anyhow::{anyhow, Result};
use async_stream::try_stream;
use bytes::Bytes;
Expand Down Expand Up @@ -88,10 +87,11 @@ where

let client = reqwest::Client::new();

let mut url = api_base.clone();
url.set_path(&v0alpha1::Route::Did.to_string());

let did_response = client.get(url).send().await?;
let did_response = {
let mut url = api_base.clone();
url.set_path(&v0alpha1::Route::Did.to_string());
client.get(url).send().await?
};

match did_response.status() {
StatusCode::OK => (),
Expand Down
9 changes: 9 additions & 0 deletions rust/noosphere-core/src/api/headers/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//! A collection of typed [headers::Header] implementations
//! used in gateway APIs.

#[cfg(doc)]
use headers;

mod ucan;

pub use self::ucan::*;
96 changes: 96 additions & 0 deletions rust/noosphere-core/src/api/headers/ucan.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
use crate::{authority::SUPPORTED_KEYS, data::Jwt};
use anyhow::anyhow;
use cid::Cid;
use headers::{self, Header, HeaderName, HeaderValue};
use once_cell::sync::Lazy;
use ucan::{chain::ProofChain, crypto::did::DidParser, store::UcanJwtStore};

static UCAN_NAME: Lazy<HeaderName> = Lazy::new(|| HeaderName::from_static("ucan"));

/// A typed header for the `ucan` header, a tuple of [Cid] and [Jwt],
/// adhering to the [UCAN as Bearer Token](https://github.com/ucan-wg/ucan-http-bearer-token)
/// specification.
///
/// TODO(#708): Note that in the 0.3.0 spec, a single `ucans` header is used.
/// This implementation is based on an earlier version with multiple `ucan`
/// headers.
///
/// The values are **not** validated during parsing beyond legitimate
/// looking [Cid]s and [Jwt]s here. Use [Ucan::as_proof_chain] to
/// validate and construct a [ProofChain].
jsantell marked this conversation as resolved.
Show resolved Hide resolved
pub struct Ucan(Vec<(Cid, Jwt)>);

impl Header for Ucan {
fn name() -> &'static HeaderName {
&UCAN_NAME
}

fn decode<'i, I>(values: &mut I) -> Result<Self, headers::Error>
where
I: Iterator<Item = &'i HeaderValue>,
{
let mut ucans = vec![];
for header in values.by_ref() {
let value = header.to_str().map_err(|_| headers::Error::invalid())?;
let mut parts: Vec<&str> = value.split_ascii_whitespace().take(2).collect();

let jwt: Jwt = parts
.pop()
.ok_or_else(headers::Error::invalid)?
.to_string()
.into();
let cid: Cid = parts
.pop()
.ok_or_else(headers::Error::invalid)?
.try_into()
.map_err(|_| headers::Error::invalid())?;

ucans.push((cid, jwt));
}

Ok(Ucan(ucans))
}

fn encode<E>(&self, values: &mut E)
where
E: Extend<HeaderValue>,
{
for (cid, jwt) in self.0.iter() {
let value = HeaderValue::from_str(&format!("{} {}", cid, jwt)).unwrap();
values.extend(std::iter::once(value));
}
}
}

impl Ucan {
/// Construct a [ProofChain] from the collected `ucan` header
/// values, validating the cryptographic integrity and time bounds
/// of the UCANs. Capabilities can then be assessed using the [ProofChain].
pub async fn as_proof_chain(
&self,
bearer: &headers::authorization::Bearer,
mut db: impl UcanJwtStore,
) -> anyhow::Result<ProofChain> {
for (cid, jwt) in self.0.iter() {
// TODO(#261): We need a worker process that purges garbage UCANs
let actual_cid = db.write_token(jwt.into()).await?;
if actual_cid != *cid {
return Err(anyhow!("Cid and Jwt do not match."));
}
}

let mut did_parser = DidParser::new(SUPPORTED_KEYS);
let proof_chain =
ProofChain::try_from_token_string(bearer.token(), None, &mut did_parser, &db).await?;

proof_chain.ucan().validate(None, &mut did_parser).await?;

Ok(proof_chain)
}
}

impl From<Ucan> for Vec<(Cid, Jwt)> {
fn from(value: Ucan) -> Self {
value.0
}
}
8 changes: 4 additions & 4 deletions rust/noosphere-core/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ mod client;
mod data;
mod route;

pub use client::*;
pub use data::*;
pub use route::*;

pub mod headers;
pub mod v0alpha1;
pub mod v0alpha2;

pub use client::*;
pub use data::*;
2 changes: 1 addition & 1 deletion rust/noosphere-core/src/authority/key_material.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub fn generate_ed25519_key() -> Ed25519KeyMaterial {
pub fn restore_ed25519_key(mnemonic: &str) -> Result<Ed25519KeyMaterial> {
let mnemonic = BipMnemonic::from_phrase(mnemonic, Language::English)?;
let private_key = Ed25519PrivateKey::try_from(mnemonic.entropy())?;
let public_key = Ed25519PublicKey::try_from(&private_key)?;
let public_key = Ed25519PublicKey::from(&private_key);

Ok(Ed25519KeyMaterial(public_key, Some(private_key)))
}
Expand Down
2 changes: 1 addition & 1 deletion rust/noosphere-core/src/data/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ mod sphere;
mod strings;
mod versioned_map;

pub use self::headers::*;
pub use address::*;
pub use authority::*;
pub use body_chunk::*;
pub use bundle::*;
pub use changelog::*;
pub use headers::*;
pub use link::*;
pub use memo::*;
pub use sphere::*;
Expand Down
Loading