token.rs

  1use anyhow::{Result, anyhow};
  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        Err(anyhow!(
 78            "identity is required for room_join grant, but it is none"
 79        ))?;
 80    }
 81
 82    let now = SystemTime::now();
 83
 84    let claims = ClaimGrants {
 85        iss: Cow::Borrowed(api_key),
 86        sub: identity.map(Cow::Borrowed),
 87        iat: now.duration_since(UNIX_EPOCH).unwrap().as_secs(),
 88        exp: now
 89            .add(DEFAULT_TTL)
 90            .duration_since(UNIX_EPOCH)
 91            .unwrap()
 92            .as_secs(),
 93        nbf: 0,
 94        jwtid: identity.map(Cow::Borrowed),
 95        video: video_grant,
 96    };
 97    Ok(jsonwebtoken::encode(
 98        &Header::default(),
 99        &claims,
100        &EncodingKey::from_secret(secret_key.as_ref()),
101    )?)
102}
103
104pub fn validate<'a>(token: &'a str, secret_key: &str) -> Result<ClaimGrants<'a>> {
105    let token = jsonwebtoken::decode(
106        token,
107        &DecodingKey::from_secret(secret_key.as_ref()),
108        &Validation::default(),
109    )?;
110
111    Ok(token.claims)
112}