Skip to content

Commit

Permalink
feat: Allow nullable expiry, per 0.9.0 spec. Fixes #23
Browse files Browse the repository at this point in the history
  • Loading branch information
jsantell committed Jun 5, 2023
1 parent 3d30ec8 commit 51a934b
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 24 deletions.
25 changes: 11 additions & 14 deletions ucan/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ where

pub capabilities: Vec<CapabilityIpld>,

pub expiration: u64,
pub expiration: Option<u64>,
pub not_before: Option<u64>,

pub facts: Vec<Value>,
Expand Down Expand Up @@ -279,19 +279,16 @@ where
pub fn build(self) -> Result<Signable<'a, K>> {
match &self.issuer {
Some(issuer) => match &self.audience {
Some(audience) => match self.implied_expiration() {
Some(expiration) => Ok(Signable {
issuer,
audience: audience.clone(),
not_before: self.not_before,
expiration,
facts: self.facts.clone(),
capabilities: self.capabilities.clone(),
proofs: self.proofs.clone(),
add_nonce: self.add_nonce,
}),
None => Err(anyhow!("Ambiguous lifetime")),
},
Some(audience) => Ok(Signable {
issuer,
audience: audience.clone(),
not_before: self.not_before,
expiration: self.implied_expiration(),
facts: self.facts.clone(),
capabilities: self.capabilities.clone(),
proofs: self.proofs.clone(),
add_nonce: self.add_nonce,
}),
None => Err(anyhow!("Missing audience")),
},
None => Err(anyhow!("Missing issuer")),
Expand Down
2 changes: 1 addition & 1 deletion ucan/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const PROOF_DELEGATION_SEMANTICS: ProofDelegationSemantics = ProofDelegationSema
pub struct CapabilityInfo<S: Scope, A: Action> {
pub originators: BTreeSet<String>,
pub not_before: Option<u64>,
pub expires_at: u64,
pub expires_at: Option<u64>,
pub capability: Capability<S, A>,
}

Expand Down
2 changes: 1 addition & 1 deletion ucan/src/ipld/ucan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub struct UcanIpld {

pub att: Vec<CapabilityIpld>,
pub prf: Option<Vec<Cid>>,
pub exp: u64,
pub exp: Option<u64>,
pub fct: Option<Vec<Value>>,

pub nnc: Option<String>,
Expand Down
5 changes: 3 additions & 2 deletions ucan/src/tests/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ async fn it_builds_with_a_simple_example() {

assert_eq!(ucan.issuer(), identities.alice_did);
assert_eq!(ucan.audience(), identities.bob_did);
assert_eq!(ucan.expires_at(), &expiration);
assert!(ucan.expires_at().is_some());
assert_eq!(ucan.expires_at().unwrap(), expiration);
assert!(ucan.not_before().is_some());
assert_eq!(ucan.not_before().unwrap(), not_before);
assert_eq!(ucan.facts(), &Some(vec![fact_1, fact_2]));
Expand All @@ -91,7 +92,7 @@ async fn it_builds_with_lifetime_in_seconds() {
.await
.unwrap();

assert!(*ucan.expires_at() > (now() + 290));
assert!(ucan.expires_at().unwrap() > (now() + 290));
}

#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
Expand Down
57 changes: 57 additions & 0 deletions ucan/src/tests/ucan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,60 @@ mod validate {
assert!(ucan_a != ucan_c);
}
}

mod spec_0_9_0 {
use crate::{builder::UcanBuilder, tests::fixtures::Identities};
use anyhow::Result;

#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure};

#[cfg(target_arch = "wasm32")]
wasm_bindgen_test_configure!(run_in_browser);

#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
async fn it_allows_nullable_expiry() -> Result<()> {
let identities = Identities::new().await;
let ucan = UcanBuilder::default()
.issued_by(&identities.alice_key)
.for_audience(identities.bob_did.as_str())
.build()?
.sign()
.await?;
let other_ucan = UcanBuilder::default()
.issued_by(&identities.alice_key)
.for_audience(identities.bob_did.as_str())
.with_lifetime(2000)
.build()?
.sign()
.await?;

assert_eq!(*ucan.expires_at(), None);
assert!(ucan.lifetime_ends_after(&other_ucan));
assert!(!other_ucan.lifetime_ends_after(&ucan));

assert_eq!(
serde_json::to_value(ucan.clone())?,
serde_json::json!({
"header": {
"alg": "EdDSA",
"typ": "JWT",
"ucv": crate::ucan::UCAN_VERSION
},
"payload": {
"iss": ucan.issuer(),
"aud": ucan.audience(),
"exp": serde_json::Value::Null,
"att": [],
"fct": [],
"prf": []
},
"signed_data": ucan.signed_data(),
"signature": ucan.signature()
})
);

Ok(())
}
}
18 changes: 12 additions & 6 deletions ucan/src/ucan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ pub struct UcanHeader {
pub struct UcanPayload {
pub iss: String,
pub aud: String,
pub exp: u64,
pub exp: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub nbf: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
Expand Down Expand Up @@ -99,9 +99,11 @@ impl Ucan {

/// Returns true if the UCAN has past its expiration date
pub fn is_expired(&self, now_time: Option<u64>) -> bool {
let now_time = now_time.unwrap_or_else(now);

self.payload.exp < now_time
if let Some(exp) = self.payload.exp {
exp < now_time.unwrap_or_else(now)
} else {
false
}
}

/// Raw bytes of signed data for this UCAN
Expand Down Expand Up @@ -135,7 +137,11 @@ impl Ucan {

/// Returns true if this UCAN expires no earlier than the other
pub fn lifetime_ends_after(&self, other: &Ucan) -> bool {
self.payload.exp >= other.payload.exp
match (self.payload.exp, other.payload.exp) {
(Some(exp), Some(other_exp)) => exp >= other_exp,
(Some(_), None) => false,
(None, _) => true,
}
}

/// Returns true if this UCAN's lifetime fully encompasses the other
Expand All @@ -159,7 +165,7 @@ impl Ucan {
&self.payload.prf
}

pub fn expires_at(&self) -> &u64 {
pub fn expires_at(&self) -> &Option<u64> {
&self.payload.exp
}

Expand Down

0 comments on commit 51a934b

Please sign in to comment.