1use anyhow::{anyhow, Result};
2use hmac::{Hmac, Mac};
3use jwt::{SignWithKey, VerifyWithKey};
4use serde::{Deserialize, Serialize};
5use sha2::Sha256;
6use std::{
7 borrow::Cow,
8 ops::Add,
9 time::{Duration, SystemTime, UNIX_EPOCH},
10};
11
12static DEFAULT_TTL: Duration = Duration::from_secs(6 * 60 * 60); // 6 hours
13
14#[derive(Default, Serialize, Deserialize)]
15#[serde(rename_all = "camelCase")]
16pub struct ClaimGrants<'a> {
17 pub iss: Cow<'a, str>,
18 pub sub: Option<Cow<'a, str>>,
19 pub iat: u64,
20 pub exp: u64,
21 pub nbf: u64,
22 pub jwtid: Option<Cow<'a, str>>,
23 pub video: VideoGrant<'a>,
24}
25
26#[derive(Default, Serialize, Deserialize)]
27#[serde(rename_all = "camelCase")]
28pub struct VideoGrant<'a> {
29 pub room_create: Option<bool>,
30 pub room_join: Option<bool>,
31 pub room_list: Option<bool>,
32 pub room_record: Option<bool>,
33 pub room_admin: Option<bool>,
34 pub room: Option<Cow<'a, str>>,
35 pub can_publish: Option<bool>,
36 pub can_subscribe: Option<bool>,
37 pub can_publish_data: Option<bool>,
38 pub hidden: Option<bool>,
39 pub recorder: Option<bool>,
40}
41
42impl<'a> VideoGrant<'a> {
43 pub fn to_admin(room: &'a str) -> Self {
44 Self {
45 room_admin: Some(true),
46 room: Some(Cow::Borrowed(room)),
47 ..Default::default()
48 }
49 }
50
51 pub fn to_join(room: &'a str) -> Self {
52 Self {
53 room: Some(Cow::Borrowed(room)),
54 room_join: Some(true),
55 can_publish: Some(true),
56 can_subscribe: Some(true),
57 ..Default::default()
58 }
59 }
60}
61
62pub fn create(
63 api_key: &str,
64 secret_key: &str,
65 identity: Option<&str>,
66 video_grant: VideoGrant,
67) -> Result<String> {
68 if video_grant.room_join.is_some() && identity.is_none() {
69 Err(anyhow!(
70 "identity is required for room_join grant, but it is none"
71 ))?;
72 }
73
74 let secret_key: Hmac<Sha256> = Hmac::new_from_slice(secret_key.as_bytes())?;
75
76 let now = SystemTime::now();
77
78 let claims = ClaimGrants {
79 iss: Cow::Borrowed(api_key),
80 sub: identity.map(Cow::Borrowed),
81 iat: now.duration_since(UNIX_EPOCH).unwrap().as_secs(),
82 exp: now
83 .add(DEFAULT_TTL)
84 .duration_since(UNIX_EPOCH)
85 .unwrap()
86 .as_secs(),
87 nbf: 0,
88 jwtid: identity.map(Cow::Borrowed),
89 video: video_grant,
90 };
91 Ok(claims.sign_with_key(&secret_key)?)
92}
93
94pub fn validate<'a>(token: &'a str, secret_key: &str) -> Result<ClaimGrants<'a>> {
95 let secret_key: Hmac<Sha256> = Hmac::new_from_slice(secret_key.as_bytes())?;
96 Ok(token.verify_with_key(&secret_key)?)
97}