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