//! Authorization tokens and their alias cache (`draft-ietf-moq-transport-22` §8.9, §9.1.3, //! §9.1.4), over the WebSocket fallback: a token registered in SETUP authorizes the //! session; one registered in a request is used by alias in the next; a request without a //! token is refused; a reference to an alias never registered closes the session. mod common; use common::*; use joule_moq::draft22::{Parameter, Parameters, RequestErrorCode, SessionError, Token}; use joule_moq_relay::client::ClientOptions; use joule_moq_relay::config::Grant; use joule_moq_relay::transport::TransportError; use std::time::Duration; use tokio::time::timeout; #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn tokens_by_value_and_by_alias() { timeout(Duration::from_secs(120), run()).await.expect("the test finishes in time"); } async fn run() { // The token is made here; only its hash is in the relay's configuration. let token: Vec = (0..24u8).map(|i| i.wrapping_mul(37).wrapping_add(11)).collect(); let hash = hex::encode(joule_moq_relay::auth::sha256(&token)); let server = relay(|c| { c.auth.required = true; c.auth.grants = vec![Grant { prefix: vec!["live".into()], token_sha256: hash, publish: true, subscribe: true, proof: false, unlimited: false, seek: false }]; c.limits.max_auth_token_cache_size = 1024; }) .await; let t = Tracks::new(ns(&["live", "t"]), "cam", 10, 5, 128); // A publisher whose SETUP registers the token (§9.1.4). let setup_token = Token::Register { alias: 1, token_type: 0, value: token.clone() }; let (p, _) = connect(&server, Via::WebSocket, ClientOptions { tokens: vec![setup_token], ..ClientOptions::default() }).await; let _pub = publish_tracks(&p, &t, None).await; // A subscriber with no token is refused. let (s, _) = connect(&server, Via::WebSocket, ClientOptions::default()).await; let refused = s.subscribe(t.ns.clone(), t.media.clone(), Parameters::new()).await.expect_err("no token"); assert!(refused.contains(&RequestErrorCode::UNAUTHORIZED.to_string()), "{refused}"); // It registers the token in one request, then uses it by alias in the next (§8.9). let reg = Parameters::new().with(Parameter::AuthorizationToken(Token::Register { alias: 7, token_type: 0, value: token.clone() })); s.subscribe(t.ns.clone(), t.media.clone(), reg).await.expect("authorized by the token it registers"); let by_alias = Parameters::new().with(Parameter::AuthorizationToken(Token::UseAlias { alias: 7 })); s.subscribe(t.ns.clone(), t.receipts.clone(), by_alias).await.expect("authorized by alias"); // A wrong token is refused even when it is well formed. let wrong = Parameters::new().with(Parameter::AuthorizationToken(Token::UseValue { token_type: 0, value: b"not-it".to_vec() })); assert!(s.subscribe(t.ns.clone(), t.media.clone(), wrong).await.is_err()); // A reference to an alias never registered closes the session (§8.9). let (x, _) = connect(&server, Via::WebSocket, ClientOptions::default()).await; let unknown = Parameters::new().with(Parameter::AuthorizationToken(Token::UseAlias { alias: 99 })); assert!(x.subscribe(t.ns.clone(), t.media.clone(), unknown).await.is_err()); match timeout(STEP, x.conn.closed()).await.expect("closed in time") { TransportError::Closed { code, .. } => assert_eq!(code, SessionError::UNKNOWN_AUTH_TOKEN_ALIAS.0), other => panic!("{other:?}"), } s.close(0, "done"); p.close(0, "done"); server.shutdown().await; }