use anyhow::{anyhow, Result}; use hmac::{Hmac, Mac}; use jwt::{SignWithKey, VerifyWithKey}; use serde::{Deserialize, Serialize}; use sha2::Sha256; use std::{ borrow::Cow, ops::Add, time::{Duration, SystemTime, UNIX_EPOCH}, }; static DEFAULT_TTL: Duration = Duration::from_secs(6 * 60 * 60); // 6 hours #[derive(Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ClaimGrants<'a> { pub iss: Cow<'a, str>, pub sub: Option>, pub iat: u64, pub exp: u64, pub nbf: u64, pub jwtid: Option>, pub video: VideoGrant<'a>, } #[derive(Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct VideoGrant<'a> { pub room_create: Option, pub room_join: Option, pub room_list: Option, pub room_record: Option, pub room_admin: Option, pub room: Option>, pub can_publish: Option, pub can_subscribe: Option, pub can_publish_data: Option, pub hidden: Option, pub recorder: Option, } impl<'a> VideoGrant<'a> { pub fn to_admin(room: &'a str) -> Self { Self { room_admin: Some(true), room: Some(Cow::Borrowed(room)), ..Default::default() } } pub fn to_join(room: &'a str) -> Self { Self { room: Some(Cow::Borrowed(room)), room_join: Some(true), can_publish: Some(true), can_subscribe: Some(true), ..Default::default() } } pub fn for_guest(room: &'a str) -> Self { Self { room: Some(Cow::Borrowed(room)), room_join: Some(true), can_subscribe: Some(true), ..Default::default() } } } pub fn create( api_key: &str, secret_key: &str, identity: Option<&str>, video_grant: VideoGrant, ) -> Result { if video_grant.room_join.is_some() && identity.is_none() { Err(anyhow!( "identity is required for room_join grant, but it is none" ))?; } let secret_key: Hmac = Hmac::new_from_slice(secret_key.as_bytes())?; let now = SystemTime::now(); let claims = ClaimGrants { iss: Cow::Borrowed(api_key), sub: identity.map(Cow::Borrowed), iat: now.duration_since(UNIX_EPOCH).unwrap().as_secs(), exp: now .add(DEFAULT_TTL) .duration_since(UNIX_EPOCH) .unwrap() .as_secs(), nbf: 0, jwtid: identity.map(Cow::Borrowed), video: video_grant, }; Ok(claims.sign_with_key(&secret_key)?) } pub fn validate<'a>(token: &'a str, secret_key: &str) -> Result> { let secret_key: Hmac = Hmac::new_from_slice(secret_key.as_bytes())?; Ok(token.verify_with_key(&secret_key)?) }