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 pub fn for_guest(room: &'a str) -> Self {
62 Self {
63 room: Some(Cow::Borrowed(room)),
64 room_join: Some(true),
65 can_publish: Some(false),
66 can_subscribe: Some(true),
67 ..Default::default()
68 }
69 }
70}
71
72pub fn create(
73 api_key: &str,
74 secret_key: &str,
75 identity: Option<&str>,
76 video_grant: VideoGrant,
77) -> Result<String> {
78 if video_grant.room_join.is_some() && identity.is_none() {
79 Err(anyhow!(
80 "identity is required for room_join grant, but it is none"
81 ))?;
82 }
83
84 let secret_key: Hmac<Sha256> = Hmac::new_from_slice(secret_key.as_bytes())?;
85
86 let now = SystemTime::now();
87
88 let claims = ClaimGrants {
89 iss: Cow::Borrowed(api_key),
90 sub: identity.map(Cow::Borrowed),
91 iat: now.duration_since(UNIX_EPOCH).unwrap().as_secs(),
92 exp: now
93 .add(DEFAULT_TTL)
94 .duration_since(UNIX_EPOCH)
95 .unwrap()
96 .as_secs(),
97 nbf: 0,
98 jwtid: identity.map(Cow::Borrowed),
99 video: video_grant,
100 };
101 Ok(claims.sign_with_key(&secret_key)?)
102}
103
104pub fn validate<'a>(token: &'a str, secret_key: &str) -> Result<ClaimGrants<'a>> {
105 let secret_key: Hmac<Sha256> = Hmac::new_from_slice(secret_key.as_bytes())?;
106 Ok(token.verify_with_key(&secret_key)?)
107}