token.rs

  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}