feat: import Chinese-localized Buzz source snapshot
Docker image / Build (linux/amd64) (push) Has been cancelled
Docker image / Build (linux/arm64) (push) Has been cancelled
Docker image / Merge release multi-arch manifest (push) Has been cancelled
Docker image / Merge debug multi-arch manifest (push) Has been cancelled
Docker image / Build public push gateway (linux/amd64) (push) Has been cancelled
Docker image / Build public push gateway (linux/arm64) (push) Has been cancelled
Docker image / Publish public push gateway image (push) Has been cancelled
Sprig image / Build (linux/amd64) (push) Has been cancelled
Sprig image / Build (linux/arm64) (push) Has been cancelled
Sprig image / Merge multi-arch manifest (push) Has been cancelled
Harbor Buzz Orchestra / Python tests and lint (push) Has been cancelled
CI / Detect Changed Paths (push) Has been cancelled
CI / Rust Lint (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Desktop Core (push) Has been cancelled
CI / Desktop Smoke E2E (1) (push) Has been cancelled
CI / Desktop Smoke E2E (2) (push) Has been cancelled
CI / Desktop Smoke E2E (3) (push) Has been cancelled
CI / Desktop Smoke E2E (4) (push) Has been cancelled
CI / Desktop (push) Has been cancelled
CI / Desktop E2E Relay (push) Has been cancelled
CI / Desktop E2E Integration (1/2) (push) Has been cancelled
CI / Desktop E2E Integration (2/2) (push) Has been cancelled
CI / Desktop E2E Integration (push) Has been cancelled
CI / Backend Integration (relay e2e) (push) Has been cancelled
CI / Relay E2E (push) Has been cancelled
CI / Web (push) Has been cancelled
CI / Mobile (push) Has been cancelled
CI / Security (push) Has been cancelled
CI / Dead Token Reference Guard (push) Has been cancelled
CI / Server Cross-Compile (aarch64-unknown-linux-musl) (push) Has been cancelled
CI / Server Cross-Compile (x86_64-unknown-linux-musl) (push) Has been cancelled
CI / Windows Rust (x86_64-pc-windows-msvc) (push) Has been cancelled
CI / Desktop Build (macOS) (push) Has been cancelled
helm chart / lint + unittest + render matrix (push) Has been cancelled
helm chart / install on kind (gated) (push) Has been cancelled
helm chart / publish chart to GHCR (push) Has been cancelled
Mesh Lifecycle / Relay-Driven Mesh Lifecycle Smoke (push) Has been cancelled
Sprig / Build (aarch64-unknown-linux-musl) (push) Has been cancelled
Sprig / Build (x86_64-unknown-linux-musl) (push) Has been cancelled
Sprig / Publish rolling release (push) Has been cancelled
Sprig / Publish tagged release (push) Has been cancelled
Docker image / Build (linux/amd64) (push) Has been cancelled
Docker image / Build (linux/arm64) (push) Has been cancelled
Docker image / Merge release multi-arch manifest (push) Has been cancelled
Docker image / Merge debug multi-arch manifest (push) Has been cancelled
Docker image / Build public push gateway (linux/amd64) (push) Has been cancelled
Docker image / Build public push gateway (linux/arm64) (push) Has been cancelled
Docker image / Publish public push gateway image (push) Has been cancelled
Sprig image / Build (linux/amd64) (push) Has been cancelled
Sprig image / Build (linux/arm64) (push) Has been cancelled
Sprig image / Merge multi-arch manifest (push) Has been cancelled
Harbor Buzz Orchestra / Python tests and lint (push) Has been cancelled
CI / Detect Changed Paths (push) Has been cancelled
CI / Rust Lint (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Desktop Core (push) Has been cancelled
CI / Desktop Smoke E2E (1) (push) Has been cancelled
CI / Desktop Smoke E2E (2) (push) Has been cancelled
CI / Desktop Smoke E2E (3) (push) Has been cancelled
CI / Desktop Smoke E2E (4) (push) Has been cancelled
CI / Desktop (push) Has been cancelled
CI / Desktop E2E Relay (push) Has been cancelled
CI / Desktop E2E Integration (1/2) (push) Has been cancelled
CI / Desktop E2E Integration (2/2) (push) Has been cancelled
CI / Desktop E2E Integration (push) Has been cancelled
CI / Backend Integration (relay e2e) (push) Has been cancelled
CI / Relay E2E (push) Has been cancelled
CI / Web (push) Has been cancelled
CI / Mobile (push) Has been cancelled
CI / Security (push) Has been cancelled
CI / Dead Token Reference Guard (push) Has been cancelled
CI / Server Cross-Compile (aarch64-unknown-linux-musl) (push) Has been cancelled
CI / Server Cross-Compile (x86_64-unknown-linux-musl) (push) Has been cancelled
CI / Windows Rust (x86_64-pc-windows-msvc) (push) Has been cancelled
CI / Desktop Build (macOS) (push) Has been cancelled
helm chart / lint + unittest + render matrix (push) Has been cancelled
helm chart / install on kind (gated) (push) Has been cancelled
helm chart / publish chart to GHCR (push) Has been cancelled
Mesh Lifecycle / Relay-Driven Mesh Lifecycle Smoke (push) Has been cancelled
Sprig / Build (aarch64-unknown-linux-musl) (push) Has been cancelled
Sprig / Build (x86_64-unknown-linux-musl) (push) Has been cancelled
Sprig / Publish rolling release (push) Has been cancelled
Sprig / Publish tagged release (push) Has been cancelled
Signed-off-by: cls_宁波本机 <908705107@qq.com>
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
//! Live two-host proof for the NIP-42 host-binding fix.
|
||||
//!
|
||||
//! Sibling of the NIP-98 row 44 obligation: AUTH event's `relay` tag must match
|
||||
//! the per-tenant host of the connection it arrives on, not the deployment-wide
|
||||
//! `config.relay_url`.
|
||||
//!
|
||||
//! Requires a running multi-tenant relay with TWO seeded communities. Bring-up:
|
||||
//!
|
||||
//! ```sh
|
||||
//! # Compose up infra, schema, then seed:
|
||||
//! INSERT INTO communities (id, host) VALUES
|
||||
//! ('11111111-1111-4111-8111-111111111111', 'a.localhost:3100'),
|
||||
//! ('22222222-2222-4222-8222-222222222222', 'b.localhost:3100');
|
||||
//! # Run one binary, BUZZ_HEALTH_PORT=8180 BUZZ_METRICS_PORT=9202,
|
||||
//! # BUZZ_RECONCILE_CHANNELS=false, BUZZ_GIT_CONFORMANCE_PROBE=false
|
||||
//! ```
|
||||
//!
|
||||
//! Each test is `#[ignore]` so it only runs explicitly:
|
||||
//! `cargo test -p buzz-test-client --test nip42_host_binding_live -- --ignored --test-threads=1`
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
use nostr::{EventBuilder, Keys, RelayUrl};
|
||||
use serde_json::{json, Value};
|
||||
use tokio_tungstenite::{connect_async, tungstenite::Message};
|
||||
|
||||
const HOST_A: &str = "ws://a.localhost:3100";
|
||||
const HOST_B: &str = "ws://b.localhost:3100";
|
||||
|
||||
/// Connect, wait for AUTH challenge, send a kind:22242 with `relay_tag_url`,
|
||||
/// return the OK response's accepted flag and message.
|
||||
async fn do_auth_with_relay_tag(
|
||||
connect_url: &str,
|
||||
relay_tag_url: &str,
|
||||
) -> Result<(bool, String), String> {
|
||||
let (mut ws, _) = connect_async(connect_url)
|
||||
.await
|
||||
.map_err(|e| format!("connect: {e}"))?;
|
||||
|
||||
// Wait for AUTH challenge.
|
||||
let challenge = loop {
|
||||
let msg = tokio::time::timeout(Duration::from_secs(5), ws.next())
|
||||
.await
|
||||
.map_err(|_| "timeout waiting for AUTH challenge".to_string())?
|
||||
.ok_or_else(|| "ws closed before challenge".to_string())?
|
||||
.map_err(|e| format!("ws read: {e}"))?;
|
||||
let text = match msg {
|
||||
Message::Text(t) => t,
|
||||
Message::Binary(_) | Message::Ping(_) | Message::Pong(_) => continue,
|
||||
other => return Err(format!("unexpected ws frame: {other:?}")),
|
||||
};
|
||||
let v: Value = serde_json::from_str(&text).map_err(|e| format!("json: {e}"))?;
|
||||
if v.get(0).and_then(|s| s.as_str()) == Some("AUTH") {
|
||||
break v
|
||||
.get(1)
|
||||
.and_then(|s| s.as_str())
|
||||
.ok_or_else(|| "AUTH msg missing challenge".to_string())?
|
||||
.to_string();
|
||||
}
|
||||
// ignore NOTICE etc.
|
||||
};
|
||||
|
||||
// Sign a NIP-42 AUTH event with chosen relay tag.
|
||||
let keys = Keys::generate();
|
||||
let parsed: RelayUrl = relay_tag_url
|
||||
.parse()
|
||||
.map_err(|e| format!("parse relay tag url {relay_tag_url}: {e}"))?;
|
||||
let event = EventBuilder::auth(&challenge, parsed)
|
||||
.sign_with_keys(&keys)
|
||||
.map_err(|e| format!("sign: {e}"))?;
|
||||
let event_id_hex = event.id.to_hex();
|
||||
|
||||
let send = json!(["AUTH", event]);
|
||||
ws.send(Message::Text(send.to_string().into()))
|
||||
.await
|
||||
.map_err(|e| format!("ws send: {e}"))?;
|
||||
|
||||
// Wait for OK with matching event id.
|
||||
loop {
|
||||
let msg = tokio::time::timeout(Duration::from_secs(5), ws.next())
|
||||
.await
|
||||
.map_err(|_| "timeout waiting for OK".to_string())?
|
||||
.ok_or_else(|| "ws closed before OK".to_string())?
|
||||
.map_err(|e| format!("ws read OK: {e}"))?;
|
||||
let text = match msg {
|
||||
Message::Text(t) => t,
|
||||
_ => continue,
|
||||
};
|
||||
let v: Value = serde_json::from_str(&text).map_err(|e| format!("json OK: {e}"))?;
|
||||
if v.get(0).and_then(|s| s.as_str()) == Some("OK")
|
||||
&& v.get(1).and_then(|s| s.as_str()) == Some(&event_id_hex)
|
||||
{
|
||||
let accepted = v.get(2).and_then(|s| s.as_bool()).unwrap_or(false);
|
||||
let message = v.get(3).and_then(|s| s.as_str()).unwrap_or("").to_string();
|
||||
return Ok((accepted, message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// AUTH event signed for host A's URL on a connection to host A → ACCEPT.
|
||||
#[tokio::test]
|
||||
#[ignore = "requires two-host multi-tenant relay"]
|
||||
async fn nip42_matching_host_accepted_a() {
|
||||
let (accepted, msg) = do_auth_with_relay_tag(HOST_A, HOST_A)
|
||||
.await
|
||||
.expect("auth flow on host A");
|
||||
assert!(
|
||||
accepted,
|
||||
"matching-host AUTH on host A must be ACCEPTED; relay said: {msg}"
|
||||
);
|
||||
}
|
||||
|
||||
/// AUTH event signed for host B's URL on a connection to host B → ACCEPT.
|
||||
#[tokio::test]
|
||||
#[ignore = "requires two-host multi-tenant relay"]
|
||||
async fn nip42_matching_host_accepted_b() {
|
||||
let (accepted, msg) = do_auth_with_relay_tag(HOST_B, HOST_B)
|
||||
.await
|
||||
.expect("auth flow on host B");
|
||||
assert!(
|
||||
accepted,
|
||||
"matching-host AUTH on host B must be ACCEPTED; relay said: {msg}"
|
||||
);
|
||||
}
|
||||
|
||||
/// Cross-host attack on the B-bound connection: forge AUTH with `relay` tag
|
||||
/// pointing at host A. Pre-fix this passed (verified against
|
||||
/// `state.config.relay_url`). Post-fix the per-tenant host check rejects it.
|
||||
#[tokio::test]
|
||||
#[ignore = "requires two-host multi-tenant relay"]
|
||||
async fn nip42_cross_host_rejected_a_relay_tag_on_b_connection() {
|
||||
let (accepted, msg) = do_auth_with_relay_tag(HOST_B, HOST_A)
|
||||
.await
|
||||
.expect("auth flow with cross-host relay tag");
|
||||
assert!(
|
||||
!accepted,
|
||||
"cross-host AUTH (relay-tag=A on connection=B) must be REJECTED; relay said: {msg}"
|
||||
);
|
||||
// Must be the host-binding rejection, not some other error.
|
||||
assert!(
|
||||
msg.contains("auth-required") || msg.contains("verification"),
|
||||
"rejection must be the NIP-42 verification-failure signal; relay said: {msg}"
|
||||
);
|
||||
}
|
||||
|
||||
/// Mirror of the cross-host test in the opposite direction: B-relay-tag on
|
||||
/// A-connection → REJECT.
|
||||
#[tokio::test]
|
||||
#[ignore = "requires two-host multi-tenant relay"]
|
||||
async fn nip42_cross_host_rejected_b_relay_tag_on_a_connection() {
|
||||
let (accepted, msg) = do_auth_with_relay_tag(HOST_A, HOST_B)
|
||||
.await
|
||||
.expect("auth flow with mirror cross-host relay tag");
|
||||
assert!(
|
||||
!accepted,
|
||||
"cross-host AUTH (relay-tag=B on connection=A) must be REJECTED; relay said: {msg}"
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user