Skip to content

Commit f7ff25c

Browse files
authored
feat: identity key from URL (#70)
1 parent e504051 commit f7ff25c

File tree

3 files changed

+148
-12
lines changed

3 files changed

+148
-12
lines changed

justfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ clippy:
5353

5454
if command -v cargo-clippy >/dev/null; then
5555
echo '==> Running clippy'
56-
cargo clippy --workspace --all-features --all-targets -- -D clippy::all -W clippy::style
56+
cargo clippy --workspace --all-features --all-targets -- -D warnings
5757
else
5858
echo '==> clippy not found in PATH, skipping'
5959
echo ' ^^^^^^ To install `rustup component add clippy`, see https://github.com/rust-lang/rust-clippy for details'

relay_rpc/src/auth/cacao/payload.rs

+127-5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use {
22
super::{CacaoError, Version},
33
crate::auth::did::{extract_did_data, DID_METHOD_KEY},
44
serde::{Deserialize, Serialize},
5+
url::Url,
56
};
67

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

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

82+
fn extract_did_key(did_key: &str) -> Result<String, CacaoError> {
83+
extract_did_data(did_key, DID_METHOD_KEY)
84+
.map_err(|_| CacaoError::PayloadIdentityKey)
85+
.map(|data| data.to_owned())
86+
}
87+
8088
fn identity_key_from_resources(&self) -> Result<String, CacaoError> {
8189
let resources = self
8290
.resources
8391
.as_ref()
8492
.ok_or(CacaoError::PayloadResources)?;
8593
let did_key = resources.first().ok_or(CacaoError::PayloadIdentityKey)?;
8694

87-
extract_did_data(did_key, DID_METHOD_KEY)
88-
.map(|data| data.to_string())
89-
.map_err(|_| CacaoError::PayloadIdentityKey)
95+
Self::extract_did_key(did_key)
9096
}
9197

9298
fn identity_key_from_audience(&self) -> Result<String, CacaoError> {
93-
extract_did_data(&self.aud, DID_METHOD_KEY)
94-
.map(|data| data.to_string())
99+
self.identity_key_from_audience_url()
100+
.or_else(|_| self.identity_key_from_audience_did_key())
101+
}
102+
103+
fn identity_key_from_audience_did_key(&self) -> Result<String, CacaoError> {
104+
Self::extract_did_key(&self.aud)
105+
}
106+
107+
fn identity_key_from_audience_url(&self) -> Result<String, CacaoError> {
108+
self.aud
109+
.parse::<Url>()
95110
.map_err(|_| CacaoError::PayloadIdentityKey)
111+
.and_then(|url| {
112+
url.query_pairs()
113+
.find(|(key, _)| key == Self::WALLETCONNECT_IDENTITY_KEY)
114+
.ok_or(CacaoError::PayloadIdentityKey)
115+
.and_then(|(_, value)| Self::extract_did_key(&value))
116+
})
117+
}
118+
}
119+
120+
#[cfg(test)]
121+
mod tests {
122+
use super::*;
123+
124+
#[test]
125+
fn identity_key_from_resources() {
126+
assert_eq!(
127+
Payload {
128+
domain: "example.com".to_owned(),
129+
iss: "did:pkh:eip155:1:0xdFe7d0E324ed017a74aE311E9236E6CaDB24176b".to_owned(),
130+
statement: None,
131+
aud: "".to_owned(),
132+
version: Version::V1,
133+
nonce: "".to_owned(),
134+
iat: "2023-09-07T11:04:23+02:00".to_owned(),
135+
exp: None,
136+
nbf: None,
137+
request_id: None,
138+
resources: Some(vec![
139+
"did:key:z6MkvjNoiz9AXGH1igzrtB54US5hE9bZPQm1ryKGkCLwWht7".to_owned(),
140+
]),
141+
}
142+
.identity_key()
143+
.unwrap(),
144+
"z6MkvjNoiz9AXGH1igzrtB54US5hE9bZPQm1ryKGkCLwWht7"
145+
);
146+
}
147+
148+
#[test]
149+
fn identity_key_from_aud() {
150+
assert_eq!(
151+
Payload {
152+
domain: "example.com".to_owned(),
153+
iss: "did:pkh:eip155:1:0xdFe7d0E324ed017a74aE311E9236E6CaDB24176b".to_owned(),
154+
statement: None,
155+
aud: "did:key:z6MkvjNoiz9AXGH1igzrtB54US5hE9bZPQm1ryKGkCLwWht7".to_owned(),
156+
version: Version::V1,
157+
nonce: "".to_owned(),
158+
iat: "2023-09-07T11:04:23+02:00".to_owned(),
159+
exp: None,
160+
nbf: None,
161+
request_id: None,
162+
resources: Some(vec![
163+
"did:key:z6MkvjNoiz9AXGH1igzrtB54US5hE9bZPQm1ryKGkCLwWht8".to_owned(),
164+
]),
165+
}
166+
.identity_key()
167+
.unwrap(),
168+
"z6MkvjNoiz9AXGH1igzrtB54US5hE9bZPQm1ryKGkCLwWht7"
169+
);
170+
}
171+
172+
#[test]
173+
fn identity_key_from_aud_url() {
174+
assert_eq!(
175+
Payload {
176+
domain: "example.com".to_owned(),
177+
iss: "did:pkh:eip155:1:0xdFe7d0E324ed017a74aE311E9236E6CaDB24176b".to_owned(),
178+
statement: None,
179+
aud: "https://example.com?walletconnect_identity_key=did:key:z6MkvjNoiz9AXGH1igzrtB54US5hE9bZPQm1ryKGkCLwWht7".to_owned(),
180+
version: Version::V1,
181+
nonce: "".to_owned(),
182+
iat: "2023-09-07T11:04:23+02:00".to_owned(),
183+
exp: None,
184+
nbf: None,
185+
request_id: None,
186+
resources: Some(vec![
187+
"did:key:z6MkvjNoiz9AXGH1igzrtB54US5hE9bZPQm1ryKGkCLwWht8".to_owned(),
188+
]),
189+
}
190+
.identity_key()
191+
.unwrap(),
192+
"z6MkvjNoiz9AXGH1igzrtB54US5hE9bZPQm1ryKGkCLwWht7"
193+
);
194+
}
195+
196+
#[test]
197+
fn identity_key_from_aud_url_encoded() {
198+
assert_eq!(
199+
Payload {
200+
domain: "example.com".to_owned(),
201+
iss: "did:pkh:eip155:1:0xdFe7d0E324ed017a74aE311E9236E6CaDB24176b".to_owned(),
202+
statement: None,
203+
aud: "https://example.com?walletconnect_identity_key=did%3Akey%3Az6MkvjNoiz9AXGH1igzrtB54US5hE9bZPQm1ryKGkCLwWht7".to_owned(),
204+
version: Version::V1,
205+
nonce: "".to_owned(),
206+
iat: "2023-09-07T11:04:23+02:00".to_owned(),
207+
exp: None,
208+
nbf: None,
209+
request_id: None,
210+
resources: Some(vec![
211+
"did:key:z6MkvjNoiz9AXGH1igzrtB54US5hE9bZPQm1ryKGkCLwWht8".to_owned(),
212+
]),
213+
}
214+
.identity_key()
215+
.unwrap(),
216+
"z6MkvjNoiz9AXGH1igzrtB54US5hE9bZPQm1ryKGkCLwWht7"
217+
);
96218
}
97219
}

relay_rpc/src/jwt.rs

+20-6
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,10 @@ mod test {
288288

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

299302
// IAT leeway, valid.
300303
let jwt = AuthToken::new(sub.clone())
301-
.iat(chrono::Utc::now() + chrono::Duration::seconds(JWT_VALIDATION_TIME_LEEWAY_SECS))
304+
.iat(
305+
chrono::Utc::now()
306+
+ chrono::Duration::try_seconds(JWT_VALIDATION_TIME_LEEWAY_SECS)
307+
.expect("Safe unwrap: does not return None"),
308+
)
302309
.as_jwt(&keypair)
303310
.unwrap();
304311
assert!(Jwt(jwt.into()).decode(&aud).is_ok());
305312

306313
// IAT leeway, invalid.
307314
let jwt = AuthToken::new(sub.clone())
308315
.iat(
309-
chrono::Utc::now() + chrono::Duration::seconds(JWT_VALIDATION_TIME_LEEWAY_SECS + 1),
316+
chrono::Utc::now()
317+
+ chrono::Duration::try_seconds(JWT_VALIDATION_TIME_LEEWAY_SECS + 1)
318+
.expect("Safe unwrap: does not return None"),
310319
)
311320
.as_jwt(&keypair)
312321
.unwrap();
@@ -317,7 +326,10 @@ mod test {
317326

318327
// Past expiration.
319328
let jwt = AuthToken::new(sub.clone())
320-
.iat(chrono::Utc::now() - chrono::Duration::hours(2))
329+
.iat(
330+
chrono::Utc::now()
331+
- chrono::Duration::try_hours(2).expect("Safe unwrap: does not return None"),
332+
)
321333
.ttl(Duration::from_secs(3600))
322334
.as_jwt(&keypair)
323335
.unwrap();
@@ -330,7 +342,8 @@ mod test {
330342
let jwt = AuthToken::new(sub.clone())
331343
.iat(
332344
chrono::Utc::now()
333-
- chrono::Duration::seconds(3600 + JWT_VALIDATION_TIME_LEEWAY_SECS),
345+
- chrono::Duration::try_seconds(3600 + JWT_VALIDATION_TIME_LEEWAY_SECS)
346+
.expect("Safe unwrap: does not return None"),
334347
)
335348
.ttl(Duration::from_secs(3600))
336349
.as_jwt(&keypair)
@@ -341,7 +354,8 @@ mod test {
341354
let jwt = AuthToken::new(sub.clone())
342355
.iat(
343356
chrono::Utc::now()
344-
- chrono::Duration::seconds(3600 + JWT_VALIDATION_TIME_LEEWAY_SECS + 1),
357+
- chrono::Duration::try_seconds(3600 + JWT_VALIDATION_TIME_LEEWAY_SECS + 1)
358+
.expect("Safe unwrap: does not return None"),
345359
)
346360
.ttl(Duration::from_secs(3600))
347361
.as_jwt(&keypair)

0 commit comments

Comments
 (0)