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}