Skip to content

Commit

Permalink
Merge pull request #18 from 3box/fix/update-ceramic-event-builder-api
Browse files Browse the repository at this point in the history
chore: update to new ceramic_event builder api
  • Loading branch information
dav1do authored Jun 21, 2024
2 parents ffa1482 + ee6fbf3 commit a3299b3
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 61 deletions.
2 changes: 1 addition & 1 deletion it/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
version: '3'
services:
ceramic-service:
image: ceramicnetwork/js-ceramic:5.1.0-rc.1
image: ceramicnetwork/js-ceramic:latest
volumes:
- ./data:/root/.ceramic
ports:
Expand Down
5 changes: 2 additions & 3 deletions src/api.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use crate::query::FilterQuery;
use crate::{jws::Jws, query::FilterQuery};
use ceramic_event::{
Base64String, Base64UrlString, Jws, MultiBase32String, MultiBase36String, StreamId,
StreamIdType,
Base64String, Base64UrlString, MultiBase32String, MultiBase36String, StreamId, StreamIdType,
};
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
Expand Down
131 changes: 131 additions & 0 deletions src/jws.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
use std::{collections::BTreeMap, str::FromStr};

use ceramic_event::{
ssi, unvalidated::signed::Signer, Base64String, Base64UrlString, Cid, MultiBase32String,
};
use serde::{Deserialize, Serialize};

/// The fields associated with the signature used to sign a JWS
#[derive(Debug, Serialize, Deserialize)]
pub struct JwsSignature {
/// Protected header
pub protected: Option<Base64String>,
/// Signature
pub signature: Base64UrlString,
}

/// Builder used to create JWS
pub struct JwsBuilder<S> {
signer: S,
additional: BTreeMap<String, serde_json::Value>,
}

impl<S: Signer> JwsBuilder<S> {
pub fn new(signer: S) -> Self {
Self {
signer,
additional: BTreeMap::new(),
}
}

pub fn with_additional(mut self, key: String, value: serde_json::Value) -> Self {
self.additional.insert(key, value);
self
}

pub fn replace_additional(mut self, additional: BTreeMap<String, serde_json::Value>) -> Self {
self.additional = additional;
self
}

pub fn build_for_cid(self, cid: &Cid) -> anyhow::Result<Jws> {
let cid_str = Base64UrlString::from_cid(cid);
let link = MultiBase32String::try_from(cid)?;
Jws::new(&self.signer, cid_str, Some(link), self.additional)
}

pub fn build_for_data<T: Serialize>(self, input: &T) -> anyhow::Result<Jws> {
let input = serde_json::to_vec(input)?;
let input = Base64UrlString::from(input);
Jws::new(&self.signer, input, None, self.additional)
}
}

/// A JWS object
#[derive(Debug, Serialize, Deserialize)]
pub struct Jws {
/// Link to CID that contains encoded data
#[serde(skip_serializing_if = "Option::is_none")]
pub link: Option<MultiBase32String>,
/// Encoded data
pub payload: Base64UrlString,
/// The signatures of the JWS
pub signatures: Vec<JwsSignature>,
}

impl Jws {
/// Create a builder for Jws objects
pub fn builder<S: Signer>(signer: S) -> JwsBuilder<S> {
JwsBuilder::new(signer)
}

/// Creates a new JWS from a payload that has already been serialized to Base64UrlString
pub fn new(
signer: &impl Signer,
input: Base64UrlString,
link: Option<MultiBase32String>,
additional_parameters: BTreeMap<String, serde_json::Value>,
) -> anyhow::Result<Self> {
let alg = signer.algorithm();
let header = ssi::jws::Header {
algorithm: alg,
type_: Some("JWT".to_string()),
key_id: Some(signer.id().id.clone()),
additional_parameters,
..Default::default()
};
// creates compact signature of protected.signature
let header_str = Base64String::from(serde_json::to_vec(&header)?);
let signing_input = format!("{}.{}", header_str.as_ref(), input.as_ref());
let signed = signer.sign(signing_input.as_bytes())?;
Ok(Self {
link,
payload: input,
signatures: vec![JwsSignature {
protected: Some(header_str),
signature: signed.into(),
}],
})
}

/// Get the payload of this jws
pub fn payload(&self) -> &Base64UrlString {
&self.payload
}

/// Get the additional parameters of the jws signature
pub fn additional(&self) -> anyhow::Result<BTreeMap<String, serde_json::Value>> {
let first = self
.signatures
.first()
.ok_or_else(|| anyhow::anyhow!("No signatures"))?;
let protected = first
.protected
.as_ref()
.ok_or_else(|| anyhow::anyhow!("No protected header"))?;
let protected = serde_json::from_slice::<ssi::jws::Header>(&protected.to_vec()?)?;
Ok(protected.additional_parameters)
}

/// Get the capability field for this jws
pub fn capability(&self) -> anyhow::Result<Cid> {
let additional = self.additional()?;
let cap = additional
.get("cap")
.ok_or_else(|| anyhow::anyhow!("No cap"))?
.as_str()
.ok_or_else(|| anyhow::anyhow!("cap is not a string"))?;
let cid = Cid::from_str(cap)?;
Ok(cid)
}
}
106 changes: 49 additions & 57 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,18 @@
#![deny(missing_docs)]
/// Structures for working with ceramic http api
pub mod api;
mod jws;
mod model_definition;
mod query;

use ceramic_event::{
event_builder::*,
unvalidated::{self, IntoSignedCeramicEvent},
Base64String, Cid, EventBytes, Jws, MultiBase36String, Signer, StreamId, StreamIdType,
unvalidated::{
signed::{Event, Signer},
Builder,
},
Base64String, Cid, MultiBase36String, StreamId, StreamIdType,
};
use jws::Jws;
use serde::Serialize;
use std::str::FromStr;

Expand Down Expand Up @@ -90,20 +94,17 @@ impl<S: Signer + Sync> CeramicHttpClient<S> {
model: &ModelDefinition,
) -> anyhow::Result<api::CreateRequest<Base64String>> {
let controller = self.signer.id().id.clone();
let parent: EventBytes = PARENT_STREAM_ID.to_vec()?.into();
let commit = Builder::default()
.with_sep(SEP.to_string())
.with_additional(SEP.to_string(), parent.into())
.init()
let parent = PARENT_STREAM_ID.to_vec();
let commit = Builder::init()
.with_controller(controller.clone())
.with_data(&model)
.build()
.await?;
let commit: unvalidated::Payload<_> = commit.into();
let commit = commit.signed(&self.signer).await?;
.with_sep(SEP.to_string(), parent)
.with_data(model)
.build();
let event = Event::from_payload(commit.into(), &self.signer)?;
let controllers: Vec<_> = vec![controller];
let data = Base64String::from(commit.linked_block.as_ref());
let model = Base64String::from(PARENT_STREAM_ID.to_vec()?);
let jws = Jws::builder(&self.signer).build_for_cid(&event.payload_cid())?;
let data = Base64String::from(event.encode_payload()?);
let model = Base64String::from(PARENT_STREAM_ID.to_vec());

Ok(api::CreateRequest {
r#type: StreamIdType::Model,
Expand All @@ -114,15 +115,15 @@ impl<S: Signer + Sync> CeramicHttpClient<S> {
model,
},
linked_block: Some(data.clone()),
jws: Some(commit.jws),
jws: Some(jws),
data: Some(data),
cacao_block: None,
},
})
}

/// Create a serde compatible request for model indexing
pub async fn create_index_model_request(
pub fn create_index_model_request(
&self,
model_id: &StreamId,
code: &str,
Expand All @@ -137,12 +138,12 @@ impl<S: Signer + Sync> CeramicHttpClient<S> {
request_path: self.index_endpoint().to_string(),
request_body: data,
};
let jws = Jws::builder(&self.signer).build_for_data(&req).await?;
let jws = Jws::builder(&self.signer).build_for_data(&req)?;
api::AdminApiRequest::try_from(jws)
}

/// Create a serde compatible request for listing indexed models
pub async fn create_list_indexed_models_request(
pub fn create_list_indexed_models_request(
&self,
code: &str,
) -> anyhow::Result<api::AdminApiRequest> {
Expand All @@ -152,20 +153,20 @@ impl<S: Signer + Sync> CeramicHttpClient<S> {
request_path: self.models_endpoint().to_string(),
request_body: data,
};
let jws = Jws::builder(&self.signer).build_for_data(&req).await?;
let jws = Jws::builder(&self.signer).build_for_data(&req)?;
api::AdminApiRequest::try_from(jws)
}

/// Create a serde compatible request for a single instance per account creation of a model
pub async fn create_single_instance_request(
pub fn create_single_instance_request(
&self,
model_id: &StreamId,
) -> anyhow::Result<api::CreateRequest<()>> {
if !model_id.is_model() {
anyhow::bail!("StreamId was not a model");
}
let controllers: Vec<_> = vec![self.signer.id().id.clone()];
let model = Base64String::from(model_id.to_vec()?);
let model = Base64String::from(model_id.to_vec());
Ok(api::CreateRequest {
r#type: StreamIdType::ModelInstanceDocument,
block: api::BlockData {
Expand Down Expand Up @@ -201,25 +202,20 @@ impl<S: Signer + Sync> CeramicHttpClient<S> {
if !model_id.is_model() {
anyhow::bail!("StreamId was not a model");
}
let model_vec = model_id.to_vec()?;
let model_vec = model_id.to_vec();
let model = Base64String::from(model_vec.as_slice());
let model_bytes = EventBytes::from(model_vec);
let unique = Self::gen_rand_bytes::<12>();
let unique: EventBytes = unique.to_vec().into();
let unique = Self::gen_rand_bytes::<12>().to_vec();
let controller = self.signer.id().id.clone();
let commit = Builder::default()
.with_sep(SEP.to_string())
.with_additional(SEP.to_string(), model_bytes.into())
.with_additional("unique".to_string(), unique.into())
.init()
let commit = Builder::init()
.with_controller(controller.clone())
.with_sep(SEP.to_string(), model_vec)
.with_unique(unique)
.with_data(data)
.build()
.await?;
let commit: unvalidated::Payload<_> = commit.into();
let commit = commit.signed(&self.signer).await?;
.build();
let event = Event::from_payload(commit.into(), &self.signer)?;
let controllers: Vec<_> = vec![controller];
let data = Base64String::from(commit.linked_block.as_ref());
let data = Base64String::from(event.encode_payload()?);
let jws = Jws::builder(&self.signer).build_for_cid(&event.payload_cid())?;

Ok(api::CreateRequest {
r#type: StreamIdType::ModelInstanceDocument,
Expand All @@ -230,7 +226,7 @@ impl<S: Signer + Sync> CeramicHttpClient<S> {
model,
},
linked_block: Some(data.clone()),
jws: Some(commit.jws),
jws: Some(jws),
data: Some(data),
cacao_block: None,
},
Expand All @@ -250,17 +246,18 @@ impl<S: Signer + Sync> CeramicHttpClient<S> {
if let Some(tip) = get.state.as_ref().and_then(|s| s.log.last()) {
let tip = Cid::from_str(tip.cid.as_ref())?;
let controller = self.signer.id().id.clone();
let model_vec = model.to_vec()?;
let model_vec = model.to_vec();
let model = Base64String::from(model_vec.as_slice());
let commit = Builder::default()
.data(get.stream_id.cid, tip, patch)
.build()
.await?;
let commit: unvalidated::Payload<_> = commit.into();
let commit = commit.signed(&self.signer).await?;
let commit = Builder::data()
.with_id(get.stream_id.cid)
.with_prev(tip)
.with_data(patch)
.build();
let event = Event::from_payload(commit.into(), &self.signer)?;
let controllers: Vec<_> = vec![controller];
let data = Base64String::from(commit.linked_block.as_ref());
let data = Base64String::from(event.encode_payload()?);
let stream = MultiBase36String::try_from(&get.stream_id)?;
let jws = Jws::builder(&self.signer).build_for_cid(&event.payload_cid())?;
Ok(api::UpdateRequest {
r#type: StreamIdType::ModelInstanceDocument,
block: api::BlockData {
Expand All @@ -270,7 +267,7 @@ impl<S: Signer + Sync> CeramicHttpClient<S> {
model,
},
linked_block: Some(data.clone()),
jws: Some(commit.jws),
jws: Some(jws),
data: Some(data),
cacao_block: None,
},
Expand Down Expand Up @@ -327,7 +324,7 @@ impl<S: Signer + Sync> CeramicHttpClient<S> {
request_path: self.node_status_endpoint().to_string(),
request_body: data,
};
let jws = Jws::builder(&self.signer).build_for_data(&req).await?;
let jws = Jws::builder(&self.signer).build_for_data(&req)?;
api::AdminApiRequest::try_from(jws)
}
}
Expand Down Expand Up @@ -393,10 +390,7 @@ pub mod remote {
.await?
.json()
.await?;
let req = self
.cli
.create_index_model_request(model_id, &resp.code)
.await?;
let req = self.cli.create_index_model_request(model_id, &resp.code)?;
let resp = self
.remote
.post(self.url_for_path(self.cli.index_endpoint())?)
Expand All @@ -419,10 +413,7 @@ pub mod remote {
.await?
.json()
.await?;
let req = self
.cli
.create_list_indexed_models_request(&resp.code)
.await?;
let req = self.cli.create_list_indexed_models_request(&resp.code)?;
let resp = self
.remote
.get(self.url_for_path(self.cli.models_endpoint())?)
Expand All @@ -442,7 +433,7 @@ pub mod remote {
&self,
model_id: &StreamId,
) -> anyhow::Result<StreamId> {
let req = self.cli.create_single_instance_request(model_id).await?;
let req = self.cli.create_single_instance_request(model_id)?;
let resp: api::StreamsResponseOrError = self
.remote
.post(self.url_for_path(self.cli.streams_endpoint())?)
Expand Down Expand Up @@ -629,7 +620,8 @@ pub mod tests {
use crate::api::Pagination;
use crate::model_definition::{GetRootSchema, ModelAccountRelation, ModelDefinition};
use crate::query::{FilterQuery, OperationFilter};
use ceramic_event::{DidDocument, JwkSigner};
use ceramic_event::unvalidated::signed::JwkSigner;
use ceramic_event::DidDocument;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
Expand Down

0 comments on commit a3299b3

Please sign in to comment.