Skip to content

Commit

Permalink
feat: identity key from URL (#70)
Browse files Browse the repository at this point in the history
  • Loading branch information
chris13524 authored Mar 7, 2024
1 parent e504051 commit f7ff25c
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 12 deletions.
2 changes: 1 addition & 1 deletion justfile
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ clippy:

if command -v cargo-clippy >/dev/null; then
echo '==> Running clippy'
cargo clippy --workspace --all-features --all-targets -- -D clippy::all -W clippy::style
cargo clippy --workspace --all-features --all-targets -- -D warnings
else
echo '==> clippy not found in PATH, skipping'
echo ' ^^^^^^ To install `rustup component add clippy`, see https://github.com/rust-lang/rust-clippy for details'
Expand Down
132 changes: 127 additions & 5 deletions relay_rpc/src/auth/cacao/payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use {
super::{CacaoError, Version},
crate::auth::did::{extract_did_data, DID_METHOD_KEY},
serde::{Deserialize, Serialize},
url::Url,
};

#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Hash)]
Expand All @@ -24,6 +25,7 @@ impl Payload {
const ISS_POSITION_OF_ADDRESS: usize = 4;
const ISS_POSITION_OF_NAMESPACE: usize = 2;
const ISS_POSITION_OF_REFERENCE: usize = 3;
pub const WALLETCONNECT_IDENTITY_KEY: &'static str = "walletconnect_identity_key";

/// TODO: write valdation
pub fn validate(&self) -> Result<(), CacaoError> {
Expand Down Expand Up @@ -77,21 +79,141 @@ impl Payload {
.or_else(|_| self.identity_key_from_resources())
}

fn extract_did_key(did_key: &str) -> Result<String, CacaoError> {
extract_did_data(did_key, DID_METHOD_KEY)
.map_err(|_| CacaoError::PayloadIdentityKey)
.map(|data| data.to_owned())
}

fn identity_key_from_resources(&self) -> Result<String, CacaoError> {
let resources = self
.resources
.as_ref()
.ok_or(CacaoError::PayloadResources)?;
let did_key = resources.first().ok_or(CacaoError::PayloadIdentityKey)?;

extract_did_data(did_key, DID_METHOD_KEY)
.map(|data| data.to_string())
.map_err(|_| CacaoError::PayloadIdentityKey)
Self::extract_did_key(did_key)
}

fn identity_key_from_audience(&self) -> Result<String, CacaoError> {
extract_did_data(&self.aud, DID_METHOD_KEY)
.map(|data| data.to_string())
self.identity_key_from_audience_url()
.or_else(|_| self.identity_key_from_audience_did_key())
}

fn identity_key_from_audience_did_key(&self) -> Result<String, CacaoError> {
Self::extract_did_key(&self.aud)
}

fn identity_key_from_audience_url(&self) -> Result<String, CacaoError> {
self.aud
.parse::<Url>()
.map_err(|_| CacaoError::PayloadIdentityKey)
.and_then(|url| {
url.query_pairs()
.find(|(key, _)| key == Self::WALLETCONNECT_IDENTITY_KEY)
.ok_or(CacaoError::PayloadIdentityKey)
.and_then(|(_, value)| Self::extract_did_key(&value))
})
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn identity_key_from_resources() {
assert_eq!(
Payload {
domain: "example.com".to_owned(),
iss: "did:pkh:eip155:1:0xdFe7d0E324ed017a74aE311E9236E6CaDB24176b".to_owned(),
statement: None,
aud: "".to_owned(),
version: Version::V1,
nonce: "".to_owned(),
iat: "2023-09-07T11:04:23+02:00".to_owned(),
exp: None,
nbf: None,
request_id: None,
resources: Some(vec![
"did:key:z6MkvjNoiz9AXGH1igzrtB54US5hE9bZPQm1ryKGkCLwWht7".to_owned(),
]),
}
.identity_key()
.unwrap(),
"z6MkvjNoiz9AXGH1igzrtB54US5hE9bZPQm1ryKGkCLwWht7"
);
}

#[test]
fn identity_key_from_aud() {
assert_eq!(
Payload {
domain: "example.com".to_owned(),
iss: "did:pkh:eip155:1:0xdFe7d0E324ed017a74aE311E9236E6CaDB24176b".to_owned(),
statement: None,
aud: "did:key:z6MkvjNoiz9AXGH1igzrtB54US5hE9bZPQm1ryKGkCLwWht7".to_owned(),
version: Version::V1,
nonce: "".to_owned(),
iat: "2023-09-07T11:04:23+02:00".to_owned(),
exp: None,
nbf: None,
request_id: None,
resources: Some(vec![
"did:key:z6MkvjNoiz9AXGH1igzrtB54US5hE9bZPQm1ryKGkCLwWht8".to_owned(),
]),
}
.identity_key()
.unwrap(),
"z6MkvjNoiz9AXGH1igzrtB54US5hE9bZPQm1ryKGkCLwWht7"
);
}

#[test]
fn identity_key_from_aud_url() {
assert_eq!(
Payload {
domain: "example.com".to_owned(),
iss: "did:pkh:eip155:1:0xdFe7d0E324ed017a74aE311E9236E6CaDB24176b".to_owned(),
statement: None,
aud: "https://example.com?walletconnect_identity_key=did:key:z6MkvjNoiz9AXGH1igzrtB54US5hE9bZPQm1ryKGkCLwWht7".to_owned(),
version: Version::V1,
nonce: "".to_owned(),
iat: "2023-09-07T11:04:23+02:00".to_owned(),
exp: None,
nbf: None,
request_id: None,
resources: Some(vec![
"did:key:z6MkvjNoiz9AXGH1igzrtB54US5hE9bZPQm1ryKGkCLwWht8".to_owned(),
]),
}
.identity_key()
.unwrap(),
"z6MkvjNoiz9AXGH1igzrtB54US5hE9bZPQm1ryKGkCLwWht7"
);
}

#[test]
fn identity_key_from_aud_url_encoded() {
assert_eq!(
Payload {
domain: "example.com".to_owned(),
iss: "did:pkh:eip155:1:0xdFe7d0E324ed017a74aE311E9236E6CaDB24176b".to_owned(),
statement: None,
aud: "https://example.com?walletconnect_identity_key=did%3Akey%3Az6MkvjNoiz9AXGH1igzrtB54US5hE9bZPQm1ryKGkCLwWht7".to_owned(),
version: Version::V1,
nonce: "".to_owned(),
iat: "2023-09-07T11:04:23+02:00".to_owned(),
exp: None,
nbf: None,
request_id: None,
resources: Some(vec![
"did:key:z6MkvjNoiz9AXGH1igzrtB54US5hE9bZPQm1ryKGkCLwWht8".to_owned(),
]),
}
.identity_key()
.unwrap(),
"z6MkvjNoiz9AXGH1igzrtB54US5hE9bZPQm1ryKGkCLwWht7"
);
}
}
26 changes: 20 additions & 6 deletions relay_rpc/src/jwt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,10 @@ mod test {

// IAT in future.
let jwt = AuthToken::new(sub.clone())
.iat(chrono::Utc::now() + chrono::Duration::hours(1))
.iat(
chrono::Utc::now()
+ chrono::Duration::try_hours(1).expect("Safe unwrap: does not return None"),
)
.as_jwt(&keypair)
.unwrap();
assert!(matches!(
Expand All @@ -298,15 +301,21 @@ mod test {

// IAT leeway, valid.
let jwt = AuthToken::new(sub.clone())
.iat(chrono::Utc::now() + chrono::Duration::seconds(JWT_VALIDATION_TIME_LEEWAY_SECS))
.iat(
chrono::Utc::now()
+ chrono::Duration::try_seconds(JWT_VALIDATION_TIME_LEEWAY_SECS)
.expect("Safe unwrap: does not return None"),
)
.as_jwt(&keypair)
.unwrap();
assert!(Jwt(jwt.into()).decode(&aud).is_ok());

// IAT leeway, invalid.
let jwt = AuthToken::new(sub.clone())
.iat(
chrono::Utc::now() + chrono::Duration::seconds(JWT_VALIDATION_TIME_LEEWAY_SECS + 1),
chrono::Utc::now()
+ chrono::Duration::try_seconds(JWT_VALIDATION_TIME_LEEWAY_SECS + 1)
.expect("Safe unwrap: does not return None"),
)
.as_jwt(&keypair)
.unwrap();
Expand All @@ -317,7 +326,10 @@ mod test {

// Past expiration.
let jwt = AuthToken::new(sub.clone())
.iat(chrono::Utc::now() - chrono::Duration::hours(2))
.iat(
chrono::Utc::now()
- chrono::Duration::try_hours(2).expect("Safe unwrap: does not return None"),
)
.ttl(Duration::from_secs(3600))
.as_jwt(&keypair)
.unwrap();
Expand All @@ -330,7 +342,8 @@ mod test {
let jwt = AuthToken::new(sub.clone())
.iat(
chrono::Utc::now()
- chrono::Duration::seconds(3600 + JWT_VALIDATION_TIME_LEEWAY_SECS),
- chrono::Duration::try_seconds(3600 + JWT_VALIDATION_TIME_LEEWAY_SECS)
.expect("Safe unwrap: does not return None"),
)
.ttl(Duration::from_secs(3600))
.as_jwt(&keypair)
Expand All @@ -341,7 +354,8 @@ mod test {
let jwt = AuthToken::new(sub.clone())
.iat(
chrono::Utc::now()
- chrono::Duration::seconds(3600 + JWT_VALIDATION_TIME_LEEWAY_SECS + 1),
- chrono::Duration::try_seconds(3600 + JWT_VALIDATION_TIME_LEEWAY_SECS + 1)
.expect("Safe unwrap: does not return None"),
)
.ttl(Duration::from_secs(3600))
.as_jwt(&keypair)
Expand Down

0 comments on commit f7ff25c

Please sign in to comment.