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