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