open-standards/joule-code/joule-code-rs/crates/joule-moq-relay/tests/auth_tokens.rs

62 lines
3.5 KiB
Rust

//! 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<u8> = (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;
}