feat: complete all WZP-S integration tasks (S-4/5/6/7/9)

WZP-S-4: Room access control
- hash_room_name() in wzp-crypto: SHA-256("featherchat-group:"+name)[:16]
- CLI --room flag hashes before SNI, web bridge does the same
- RoomManager gains ACL: with_acl(), allow(), is_authorized()
- join() returns Result, rejects unauthorized fingerprints

WZP-S-5: Crypto handshake wired into all live paths
- CLI: perform_handshake() after connect, before any mode
- Relay: accept_handshake() after auth, before room join
- Web bridge: perform_handshake() after auth, before audio
- Relay generates ephemeral identity at startup

WZP-S-6: Web bridge featherChat auth
- --auth-url flag: browsers send {"type":"auth","token":"..."} as first WS msg
- Validates against featherChat, passes token to relay
- --cert/--key flags for production TLS (replaces self-signed)

WZP-S-7: wzp-proto standalone
- Cargo.toml uses explicit versions (no workspace inheritance)
- FC can use as git dependency

WZP-S-9: All 6 hardcoded assumptions resolved
- Auth, hashed rooms, mandatory handshake, real TLS certs,
  profile negotiation, token validation

CLI also gains --room and --token flags.
179 tests passing across all crates.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-03-28 09:59:05 +04:00
parent 26dc848081
commit 59069bfba2
10 changed files with 380 additions and 110 deletions

View File

@@ -42,6 +42,8 @@ struct CliArgs {
echo_test_secs: Option<u32>,
seed_hex: Option<String>,
mnemonic: Option<String>,
room: Option<String>,
token: Option<String>,
}
impl CliArgs {
@@ -78,6 +80,8 @@ fn parse_args() -> CliArgs {
let mut echo_test_secs = None;
let mut seed_hex = None;
let mut mnemonic = None;
let mut room = None;
let mut token = None;
let mut relay_str = None;
let mut i = 1;
@@ -116,6 +120,14 @@ fn parse_args() -> CliArgs {
i -= 1; // back up since outer loop will increment
mnemonic = Some(words.join(" "));
}
"--room" => {
i += 1;
room = Some(args.get(i).expect("--room requires a name").to_string());
}
"--token" => {
i += 1;
token = Some(args.get(i).expect("--token requires a value").to_string());
}
"--record" => {
i += 1;
record_file = Some(
@@ -144,6 +156,8 @@ fn parse_args() -> CliArgs {
eprintln!(" --echo-test <secs> Run automated echo quality test");
eprintln!(" --seed <hex> Identity seed (64 hex chars, featherChat compatible)");
eprintln!(" --mnemonic <words...> Identity seed as BIP39 mnemonic (24 words)");
eprintln!(" --room <name> Room name (hashed for privacy before sending)");
eprintln!(" --token <token> featherChat bearer token for relay auth");
eprintln!(" (48kHz mono s16le, play with ffplay -f s16le -ar 48000 -ch_layout mono file.raw)");
eprintln!();
eprintln!("Default relay: 127.0.0.1:4433");
@@ -175,6 +189,8 @@ fn parse_args() -> CliArgs {
echo_test_secs,
seed_hex,
mnemonic,
room,
token,
}
}
@@ -183,16 +199,27 @@ async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt().init();
let cli = parse_args();
let _seed = cli.resolve_seed();
let seed = cli.resolve_seed();
info!(
relay = %cli.relay_addr,
live = cli.live,
send_tone = ?cli.send_tone_secs,
record = ?cli.record_file,
room = ?cli.room,
"WarzonePhone client"
);
// Hash room name for SNI privacy (or "default" if none specified)
let sni = match &cli.room {
Some(name) => {
let hashed = wzp_crypto::hash_room_name(name);
info!(room = %name, hashed = %hashed, "room name hashed for SNI");
hashed
}
None => "default".to_string(),
};
let client_config = wzp_transport::client_config();
let bind_addr = if cli.relay_addr.is_ipv6() {
"[::]:0".parse()?
@@ -201,12 +228,28 @@ async fn main() -> anyhow::Result<()> {
};
let endpoint = wzp_transport::create_endpoint(bind_addr, None)?;
let connection =
wzp_transport::connect(&endpoint, cli.relay_addr, "localhost", client_config).await?;
wzp_transport::connect(&endpoint, cli.relay_addr, &sni, client_config).await?;
info!("Connected to relay");
let transport = Arc::new(wzp_transport::QuinnTransport::new(connection));
// Send auth token if provided (relay with --auth-url expects this first)
if let Some(ref token) = cli.token {
let auth = wzp_proto::SignalMessage::AuthToken {
token: token.clone(),
};
transport.send_signal(&auth).await?;
info!("auth token sent");
}
// Crypto handshake — establishes verified identity + session key
let _crypto_session = wzp_client::handshake::perform_handshake(
&*transport,
&seed.0,
).await?;
info!("crypto handshake complete");
if cli.live {
#[cfg(feature = "audio")]
{