Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: update to new ceramic_event builder api #18

Merged
merged 3 commits into from
Jun 21, 2024
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
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
Loading