1mod capability;
2
3pub use capability::{Capability, DecodingError, EncodingError, VerificationError};
4pub use ucan_capabilities_object::{
5 AbilityName, AbilityNameRef, AbilityNamespace, AbilityNamespaceRef, AbilityRef, CapsInner,
6 ConvertError, NotaBeneCollection,
7};
8
9pub const RESOURCE_PREFIX: &str = "urn:recap:";
11
12#[cfg(test)]
13mod test {
14 use super::*;
15 use serde_json::Value;
16 use siws::message::SiwsMessage;
17
18 const SIWE_WITH_INTERLEAVED_RES: &str =
19 include_str!("../tests/siwe_with_interleaved_resources.txt");
20 const SIWE_WITH_STATEMENT_NO_CAPS: &str =
21 include_str!("../tests/siwe_with_statement_no_caps.txt");
22 const SIWE_WITH_STATEMENT: &str = include_str!("../tests/siwe_with_statement.txt");
23 const SIWE_NO_CAPS: &str = include_str!("../tests/siwe_with_no_caps.txt");
24 const SIWE: &str = include_str!("../tests/siwe_with_caps.txt");
25
26 #[test]
27 fn no_caps_statement_append() {
28 let msg = Capability::<Value>::default()
29 .build_message(SiwsMessage {
30 domain: "example.com".parse().unwrap(),
31 address: "0000000000000000000000000000000000000000".into(),
32 statement: Some("Some custom statement.".into()),
33 uri: Some("did:key:example".parse().unwrap()),
34 version: Some("1".into()),
35 chain_id: Some("testnet".into()),
36 nonce: Some("mynonce1".into()),
37 issued_at: Some("2022-06-21T12:00:00.000Z".parse().unwrap()),
38 expiration_time: None,
39 not_before: None,
40 request_id: None,
41 resources: vec![],
42 })
43 .expect("failed to build SIWE delegation");
44
45 assert_eq!(
46 SIWE_WITH_STATEMENT_NO_CAPS,
47 msg.to_string(),
48 "generated SIWE message did not match expectation"
49 );
50 }
51
52 #[test]
53 fn build_delegation_statement_append() {
54 let mut cap = Capability::<Value>::default();
55 cap.with_action_convert("credential:*", "credential/present", [])
56 .unwrap();
57
58 let msg = cap
59 .build_message(SiwsMessage {
60 domain: "example.com".parse().unwrap(),
61 address: "0000000000000000000000000000000000000000".into(),
62 statement: Some("Some custom statement.".into()),
63 uri: Some("did:key:example".parse().unwrap()),
64 version: Some("1".into()),
65 chain_id: Some("testnet".into()),
66 nonce: Some("mynonce1".into()),
67 issued_at: Some("2022-06-21T12:00:00.000Z".parse().unwrap()),
68 expiration_time: None,
69 not_before: None,
70 request_id: None,
71 resources: vec!["http://example.com".parse().unwrap()],
72 })
73 .expect("failed to build SIWE delegation");
74
75 assert_eq!(
76 SIWE_WITH_STATEMENT.trim(),
77 msg.to_string(),
78 "generated SIWE message did not match expectation"
79 );
80 }
81
82 #[test]
83 fn no_caps() {
84 let msg = Capability::<Value>::default()
85 .build_message(SiwsMessage {
86 domain: "example.com".parse().unwrap(),
87 address: "0000000000000000000000000000000000000000".into(),
88 statement: None,
89 uri: Some("did:key:example".parse().unwrap()),
90 version: Some("1".into()),
91 chain_id: Some("testnet".into()),
92 nonce: Some("mynonce1".into()),
93 issued_at: Some("2022-06-21T12:00:00.000Z".parse().unwrap()),
94 expiration_time: None,
95 not_before: None,
96 request_id: None,
97 resources: vec![],
98 })
99 .expect("failed to build SIWE delegation");
100
101 assert_eq!(
102 SIWE_NO_CAPS,
103 msg.to_string(),
104 "generated SIWE message did not match expectation"
105 );
106 }
107
108 #[test]
109 fn build_delegation() {
110 let msg = Capability::<Value>::default()
111 .with_actions_convert("urn:credential:type:type1", [("credential/present", [])])
112 .unwrap()
113 .with_actions_convert(
114 "kepler:ens:example.eth://default/kv",
115 [("kv/list", []), ("kv/get", []), ("kv/metadata", [])],
116 )
117 .unwrap()
118 .with_actions_convert(
119 "kepler:ens:example.eth://default/kv/public",
120 [
121 ("kv/list", []),
122 ("kv/get", []),
123 ("kv/metadata", []),
124 ("kv/put", []),
125 ("kv/delete", []),
126 ],
127 )
128 .unwrap()
129 .with_actions_convert(
130 "kepler:ens:example.eth://default/kv/dapp-space",
131 [
132 ("kv/list", []),
133 ("kv/get", []),
134 ("kv/metadata", []),
135 ("kv/put", []),
136 ("kv/delete", []),
137 ],
138 )
139 .unwrap()
140 .build_message(SiwsMessage {
141 domain: "example.com".parse().unwrap(),
142 address: "0000000000000000000000000000000000000000".into(),
143 statement: None,
144 uri: Some("did:key:example".parse().unwrap()),
145 version: Some("1".into()),
146 chain_id: Some("testnet".into()),
147 nonce: Some("mynonce1".into()),
148 issued_at: Some("2022-06-21T12:00:00.000Z".parse().unwrap()),
149 expiration_time: None,
150 not_before: None,
151 request_id: None,
152 resources: vec![],
153 })
154 .expect("failed to build SIWE delegation");
155
156 assert_eq!(
157 SIWE.trim(),
158 msg.to_string(),
159 "generated SIWE message did not match expectation"
160 );
161 }
162
163 #[test]
164 fn verify() {
165 let msg: SiwsMessage = SIWE.trim().parse().unwrap();
166 assert!(
167 Capability::<Value>::extract_and_verify(&msg)
168 .transpose()
169 .expect("unable to parse resources as capabilities")
170 .is_ok(),
171 "statement did not match capabilities"
172 );
173
174 let mut altered_msg_1 = msg.clone();
175 altered_msg_1
176 .statement
177 .iter_mut()
178 .for_each(|statement| statement.push_str(" I am the walrus!"));
179 assert!(
180 Capability::<Value>::extract_and_verify(&altered_msg_1).is_err(),
181 "altered statement incorrectly matched capabilities"
182 );
183 }
184
185 #[test]
186 fn verify_interleaved_resources() {
187 let msg: SiwsMessage = SIWE_WITH_INTERLEAVED_RES.trim().parse().unwrap();
188 assert!(
189 Capability::<Value>::extract_and_verify(&msg)
190 .unwrap()
191 .is_none(),
192 "recap resource should come last"
193 );
194 }
195}