1use std::{collections::BTreeSet, fmt, str::FromStr};
2
3use anyhow::{Context, Result, bail};
4use iroh::{EndpointId, SecretKey};
5use n0_future::time::Duration;
6use rcan::{Capability, Expires, Rcan};
7use serde::{Deserialize, Serialize};
8
9macro_rules! cap_enum(
10 ($enum:item) => {
11 #[derive(
12 Debug,
13 Eq,
14 PartialEq,
15 Ord,
16 PartialOrd,
17 Serialize,
18 Deserialize,
19 Clone,
20 Copy,
21 strum::Display,
22 strum::EnumString,
23 )]
24 #[strum(serialize_all = "kebab-case")]
25 #[serde(rename_all = "kebab-case")]
26 $enum
27 }
28);
29
30#[derive(Debug, Eq, PartialEq, Serialize, Deserialize, Clone)]
31#[serde(rename_all = "kebab-case")]
32pub enum Caps {
33 V0(CapSet<Cap>),
34}
35
36impl Default for Caps {
37 fn default() -> Self {
38 Self::V0(CapSet::default())
39 }
40}
41
42impl std::ops::Deref for Caps {
43 type Target = CapSet<Cap>;
44
45 fn deref(&self) -> &Self::Target {
46 let Self::V0(slf) = self;
47 slf
48 }
49}
50
51#[derive(
60 Debug,
61 Eq,
62 PartialEq,
63 Ord,
64 PartialOrd,
65 Serialize,
66 Deserialize,
67 Clone,
68 Copy,
69 derive_more::From,
70 strum::Display,
71)]
72#[serde(rename_all = "kebab-case")]
73pub enum Cap {
74 #[strum(to_string = "all")]
75 All,
76 #[strum(to_string = "client")]
77 Client,
78 #[strum(to_string = "relay:{0}")]
79 Relay(RelayCap),
80 #[strum(to_string = "metrics:{0}")]
81 Metrics(MetricsCap),
82 #[strum(to_string = "net-diagnostics:{0}")]
83 NetDiagnostics(NetDiagnosticsCap),
84}
85
86impl FromStr for Cap {
87 type Err = anyhow::Error;
88
89 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
90 if s == "all" {
91 Ok(Self::All)
92 } else if let Some((domain, inner)) = s.split_once(":") {
93 Ok(match domain {
94 "metrics" => Self::Metrics(MetricsCap::from_str(inner)?),
95 "relay" => Self::Relay(RelayCap::from_str(inner)?),
96 "net-diagnostics" => Self::NetDiagnostics(NetDiagnosticsCap::from_str(inner)?),
97 _ => bail!("invalid cap domain"),
98 })
99 } else {
100 Err(anyhow::anyhow!("invalid cap string"))
101 }
102 }
103}
104
105cap_enum!(
106 pub enum MetricsCap {
107 PutAny,
108 }
109);
110
111cap_enum!(
112 pub enum RelayCap {
113 Use,
114 }
115);
116
117cap_enum!(
118 pub enum NetDiagnosticsCap {
119 PutAny,
120 GetAny,
121 }
122);
123
124impl Caps {
125 pub fn new(caps: impl IntoIterator<Item = impl Into<Cap>>) -> Self {
126 Self::V0(CapSet::new(caps))
127 }
128
129 pub fn for_shared_secret() -> Self {
135 Self::new([Cap::Client])
136 }
137
138 pub fn all() -> Self {
141 Self::new([Cap::All])
142 }
143
144 pub fn extend(self, caps: impl IntoIterator<Item = impl Into<Cap>>) -> Self {
145 let Self::V0(mut set) = self;
146 set.extend(caps.into_iter().map(Into::into));
147 Self::V0(set)
148 }
149
150 pub fn from_strs<'a>(strs: impl IntoIterator<Item = &'a str>) -> Result<Self> {
151 Ok(Self::V0(CapSet::from_strs(strs)?))
152 }
153
154 pub fn to_strings(&self) -> Vec<String> {
155 let Self::V0(set) = self;
156 set.to_strings()
157 }
158}
159
160impl Capability for Caps {
161 fn permits(&self, other: &Self) -> bool {
162 let Self::V0(slf) = self;
163 let Self::V0(other) = other;
164 slf.permits(other)
165 }
166}
167
168impl From<Cap> for Caps {
169 fn from(cap: Cap) -> Self {
170 Self::new([cap])
171 }
172}
173
174impl Capability for Cap {
175 fn permits(&self, other: &Self) -> bool {
176 match (self, other) {
177 (Cap::All, _) => true,
178 (Cap::Client, other) => client_capabilities(other),
179 (Cap::Relay(slf), Cap::Relay(other)) => slf.permits(other),
180 (Cap::Metrics(slf), Cap::Metrics(other)) => slf.permits(other),
181 (Cap::NetDiagnostics(slf), Cap::NetDiagnostics(other)) => slf.permits(other),
182 (_, _) => false,
183 }
184 }
185}
186
187fn client_capabilities(other: &Cap) -> bool {
188 match other {
189 Cap::All => false,
190 Cap::Client => true,
191 Cap::Relay(RelayCap::Use) => true,
192 Cap::Metrics(MetricsCap::PutAny) => true,
193 Cap::NetDiagnostics(NetDiagnosticsCap::PutAny) => true,
194 Cap::NetDiagnostics(NetDiagnosticsCap::GetAny) => true,
195 }
196}
197
198impl Capability for MetricsCap {
199 fn permits(&self, other: &Self) -> bool {
200 match (self, other) {
201 (MetricsCap::PutAny, MetricsCap::PutAny) => true,
202 }
203 }
204}
205
206impl Capability for RelayCap {
207 fn permits(&self, other: &Self) -> bool {
208 match (self, other) {
209 (RelayCap::Use, RelayCap::Use) => true,
210 }
211 }
212}
213
214impl Capability for NetDiagnosticsCap {
215 fn permits(&self, other: &Self) -> bool {
216 match (self, other) {
217 (NetDiagnosticsCap::PutAny, NetDiagnosticsCap::PutAny) => true,
218 (NetDiagnosticsCap::GetAny, NetDiagnosticsCap::GetAny) => true,
219 (_, _) => false,
220 }
221 }
222}
223
224#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone, Serialize, Deserialize)]
226pub struct CapSet<C: Capability + Ord>(BTreeSet<C>);
227
228impl<C: Capability + Ord> Default for CapSet<C> {
229 fn default() -> Self {
230 Self(BTreeSet::new())
231 }
232}
233
234impl<C: Capability + Ord> CapSet<C> {
235 pub fn new(set: impl IntoIterator<Item = impl Into<C>>) -> Self {
236 Self(BTreeSet::from_iter(set.into_iter().map(Into::into)))
237 }
238
239 pub fn iter(&self) -> impl Iterator<Item = &'_ C> + '_ {
240 self.0.iter()
241 }
242
243 pub fn is_empty(&self) -> bool {
244 self.0.is_empty()
245 }
246
247 pub fn len(&self) -> usize {
248 self.0.len()
249 }
250
251 pub fn contains(&self, cap: impl Into<C>) -> bool {
252 let cap = cap.into();
253 self.0.contains(&cap)
254 }
255
256 pub fn extend(&mut self, caps: impl IntoIterator<Item = impl Into<C>>) {
257 self.0.extend(caps.into_iter().map(Into::into));
258 }
259
260 pub fn insert(&mut self, cap: impl Into<C>) -> bool {
261 self.0.insert(cap.into())
262 }
263
264 pub fn from_strs<'a, E>(strs: impl IntoIterator<Item = &'a str>) -> Result<Self>
265 where
266 C: FromStr<Err = E>,
267 Result<C, E>: anyhow::Context<C, E>,
268 {
269 let mut caps = Self::default();
270 for s in strs {
271 let cap = C::from_str(s).with_context(|| format!("Unknown capability: {s}"))?;
272 caps.insert(cap);
273 }
274 Ok(caps)
275 }
276
277 pub fn to_strings(&self) -> Vec<String>
278 where
279 C: fmt::Display,
280 {
281 self.iter().map(ToString::to_string).collect()
282 }
283}
284
285impl<C: Capability + Ord> Capability for CapSet<C> {
286 fn permits(&self, other: &Self) -> bool {
287 other
288 .iter()
289 .all(|other_cap| self.iter().any(|self_cap| self_cap.permits(other_cap)))
290 }
291}
292
293#[cfg(feature = "ssh-key")]
295pub fn create_api_token_from_ssh_key(
296 user_ssh_key: &ssh_key::PrivateKey,
297 local_id: EndpointId,
298 max_age: Duration,
299 capability: Caps,
300) -> Result<Rcan<Caps>> {
301 let issuer: ed25519_dalek::SigningKey = user_ssh_key
302 .key_data()
303 .ed25519()
304 .context("only Ed25519 keys supported")?
305 .private
306 .clone()
307 .into();
308
309 let audience = local_id.as_verifying_key();
310 let can =
311 Rcan::issuing_builder(&issuer, audience, capability).sign(Expires::valid_for(max_age));
312 Ok(can)
313}
314
315pub fn create_grant_token(
319 local_secret: SecretKey,
320 remote_id: EndpointId,
321 max_age: Duration,
322 capability: Caps,
323) -> Result<Rcan<Caps>> {
324 let issuer = ed25519_dalek::SigningKey::from_bytes(&local_secret.to_bytes());
325 let audience = remote_id.as_verifying_key();
326 let can =
327 Rcan::issuing_builder(&issuer, audience, capability).sign(Expires::valid_for(max_age));
328 Ok(can)
329}
330
331pub fn create_api_token_from_secret_key(
333 private_key: SecretKey,
334 local_id: EndpointId,
335 max_age: Duration,
336 capability: Caps,
337) -> Result<Rcan<Caps>> {
338 let issuer = ed25519_dalek::SigningKey::from_bytes(&private_key.to_bytes());
339 let audience = local_id.as_verifying_key();
340 let can =
341 Rcan::issuing_builder(&issuer, audience, capability).sign(Expires::valid_for(max_age));
342 Ok(can)
343}
344
345#[cfg(test)]
346mod tests {
347 use super::*;
348
349 #[test]
350 fn smoke() {
351 let all = Caps::default()
352 .extend([RelayCap::Use])
353 .extend([MetricsCap::PutAny]);
354
355 println!("all: {all:?}");
357 let strings = all.to_strings();
358 println!("strings: {strings:?}");
359 let parsed = Caps::from_strs(strings.iter().map(|s| s.as_str())).unwrap();
360 assert_eq!(all, parsed);
361
362 let s = ["metrics:put-any", "relay:use"];
364 let caps = Caps::from_strs(s).unwrap();
365 assert_eq!(
366 caps,
367 Caps::new([MetricsCap::PutAny]).extend([RelayCap::Use])
368 );
369
370 let full = Caps::new([Cap::All]);
371
372 assert!(full.permits(&full));
373 assert!(full.permits(&all));
374 assert!(!all.permits(&full));
375
376 let metrics = Caps::new([MetricsCap::PutAny]);
377 let relay = Caps::new([RelayCap::Use]);
378
379 for cap in [&metrics, &relay] {
380 assert!(full.permits(cap));
381 assert!(all.permits(cap));
382 assert!(!cap.permits(&full));
383 assert!(!cap.permits(&all));
384 }
385
386 assert!(!metrics.permits(&relay));
387 assert!(!relay.permits(&metrics));
388 }
389
390 #[test]
391 fn client_caps() {
392 let client = Caps::new([Cap::Client]);
393
394 let all = Caps::new([Cap::All]);
395 let metrics = Caps::new([MetricsCap::PutAny]);
396 let relay = Caps::new([RelayCap::Use]);
397 assert!(client.permits(&metrics));
398 assert!(client.permits(&relay));
399 assert!(!client.permits(&all));
400 }
401}