diff --git a/justfile b/justfile index 2cd7d52..e310120 100644 --- a/justfile +++ b/justfile @@ -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' diff --git a/relay_rpc/src/auth/cacao/payload.rs b/relay_rpc/src/auth/cacao/payload.rs index 117f3a8..71cf252 100644 --- a/relay_rpc/src/auth/cacao/payload.rs +++ b/relay_rpc/src/auth/cacao/payload.rs @@ -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)] @@ -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> { @@ -77,6 +79,12 @@ impl Payload { .or_else(|_| self.identity_key_from_resources()) } + fn extract_did_key(did_key: &str) -> Result { + extract_did_data(did_key, DID_METHOD_KEY) + .map_err(|_| CacaoError::PayloadIdentityKey) + .map(|data| data.to_owned()) + } + fn identity_key_from_resources(&self) -> Result { let resources = self .resources @@ -84,14 +92,128 @@ impl Payload { .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 { - 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 { + Self::extract_did_key(&self.aud) + } + + fn identity_key_from_audience_url(&self) -> Result { + self.aud + .parse::() .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" + ); } } diff --git a/relay_rpc/src/jwt.rs b/relay_rpc/src/jwt.rs index 3eab799..d9a4bf4 100644 --- a/relay_rpc/src/jwt.rs +++ b/relay_rpc/src/jwt.rs @@ -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!( @@ -298,7 +301,11 @@ 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()); @@ -306,7 +313,9 @@ mod test { // 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(); @@ -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(); @@ -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) @@ -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)