Skip to content

fix(pairing): require explicit pairing relay discovery - #6183

Draft
kunalbhatt wants to merge 1 commit into
block:mainfrom
kunalbhatt:kbhatt/FIX-mobile-pairing-discovery
Draft

fix(pairing): require explicit pairing relay discovery#6183
kunalbhatt wants to merge 1 commit into
block:mainfrom
kunalbhatt:kbhatt/FIX-mobile-pairing-discovery

Conversation

@kunalbhatt

Copy link
Copy Markdown

Summary

Fix Desktop pairing discovery so NIP-43 is treated as relay membership support,
not proof that a legacy same-host /pair WebSocket endpoint exists.

Desktop now prefers an explicitly advertised, valid pairing_relay_url. It
returns an actionable configuration error before opening a WebSocket when that
field is invalid, or when a membership-gated NIP-43 relay does not advertise
one. Non-NIP-43 relays retain their existing main-relay pairing behavior, and a
same-host /pair endpoint remains supported when explicitly advertised.

Production Compose now includes the existing stateless buzz-pair-relay
binary and an Nginx gateway. The gateway is the only service publishing host
port 3000: /pair traffic goes to the pairing relay and all other traffic goes
to the main relay. The optional Caddy overlay proxies through the gateway, and
the documentation includes direct Cloudflare Tunnel routing.

Related issue

Fixes #2734.

Related open work found before opening this PR: #2736 and #5715 add Compose
pairing sidecars but retain the incorrect NIP-43-to-/pair client fallback;
#3424 covers chart routing only. This PR includes the Desktop discovery fix,
regression coverage, and the Compose gateway architecture together.

Problem and root cause

A membership-gated relay correctly advertises NIP-43 in its NIP-11 document.
Desktop interpreted supported_nips: [43] as endpoint discovery, derived
wss://<relay>/pair, and attempted to connect. NIP-43 describes relay
membership support and says nothing about a pairing endpoint. Production
Compose exposed only the main relay on port 3000, so the invented endpoint
returned HTTP 404.

Reproduction

  1. Set BUZZ_REQUIRE_RELAY_MEMBERSHIP=true.
  2. Set a valid RELAY_OWNER_PUBKEY.
  3. Leave BUZZ_PAIRING_RELAY_URL unset.
  4. Confirm NIP-11 advertises NIP-43.
  5. Open Buzz Desktop → Settings → Mobile.
  6. Observe the old client deriving /pair and receiving HTTP 404 Not Found.

Before and after

  • Before: any NIP-43 relay without pairing_relay_url was treated as if
    <relay>/pair existed.
  • After: NIP-43 without an explicit pairing URL fails before WebSocket setup
    with guidance to configure the stateless sidecar through
    BUZZ_PAIRING_RELAY_URL.
  • Before: an invalid explicit pairing URL was ignored and could fall through
    to legacy behavior.
  • After: only ws:// or wss:// URLs with a host are accepted; invalid values
    return an actionable configuration error.
  • Unchanged: relays that do not advertise NIP-43 continue pairing against the
    main relay.
  • Unchanged: legacy same-host /pair remains usable when it is explicitly
    advertised as pairing_relay_url.

Security and deployment implications

  • Discovery fails closed before session creation or WebSocket connection for
    invalid/missing membership-gated pairing configuration.
  • Nginx is the single public Compose gateway. The main relay and pairing relay
    have no host ports when the gateway is enabled.
  • BUZZ_HTTP_BIND_IP=127.0.0.1 binds public port 3000 to loopback for a
    host-installed Cloudflare Tunnel or proxy.
  • Cloudflare Tunnel can target the gateway directly; Caddy is not required.
    Optional Caddy support routes through the gateway instead of duplicating
    /pair behavior.
  • buzz-pair-relay remains anonymous and ephemeral: it has no identity,
    database, or persistent volume.
  • No persistent data or identities are changed or migrated.
  • No secret .env, .env.backup*, backup Compose file, volume contents,
    identity key, or private-host configuration is included. The only environment
    file changed is the public .env.example, with
    BUZZ_PAIRING_RELAY_URL=wss://buzz.example.com/pair.

Testing

All commands below ran with the repository Hermit environment activated.

Command Result
. ./bin/activate-hermit Passed; Hermit environment activated (exit 0)
just desktop-tauri-fmt Passed (exit 0)
cargo fmt --all Passed (exit 0)
just desktop-tauri-fmt-check Passed (exit 0)
cargo fmt --all -- --check Passed (exit 0)
git diff --check Passed, no whitespace errors (exit 0)
just _ensure-sidecar-stubs Passed (exit 0)
cargo test --manifest-path desktop/src-tauri/Cargo.toml -p buzz-desktop pairing_relay_tests Passed: 8 passed, 0 failed
cargo test -p buzz-relay nip11::tests Passed: 15 passed, 0 failed
just desktop-tauri-clippy Passed with warnings denied (exit 0)
cargo clippy -p buzz-relay --all-targets -- -D warnings Passed (exit 0)
bash -n deploy/compose/run.sh Passed (exit 0)
just ci Passed end to end (exit 0), including 2,512 Desktop Tauri tests with 17 expected ignores and 1,465 Flutter tests

Deployment configuration validation:

  • Docker Compose v5.5.0 standalone config --format json passed for the base
    configuration using an isolated copy of the public sample config and
    BUZZ_HTTP_BIND_IP=127.0.0.1. Assertions confirmed that only the gateway
    publishes 127.0.0.1:3000, the relay and pairing relay publish no host ports,
    and the pairing relay has no volumes.
  • The optional Caddy Compose overlay rendered successfully. Assertions
    confirmed that it resets the gateway host port and waits for the healthy
    gateway.
  • BUZZ_DOMAIN=buzz.example.com caddy validate --config deploy/compose/Caddyfile --adapter caddyfile
    passed with Caddy v2.11.4, and
    the Caddyfile is canonically formatted.
  • nginx -t -c <temporary-config> passed with Nginx 1.19.4. The temporary
    config changed only the Compose-DNS upstream names to loopback so local Nginx
    could resolve them; the committed config retains relay:3000 and
    pairing-relay:5000.
  • A Docker daemon/CLI was not installed on this machine, so container startup
    and a live cross-container WebSocket handshake were not exercised locally.

Screenshots are not applicable; the Desktop change is pairing endpoint
selection and error handling, and the deployment change is proxy routing.

Signed-off-by: Kunal Bhatt <kbhatt@squareup.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

deploy/compose: mobile pairing broken out of the box — bundle ships no buzz-pair-relay and relay 404s the legacy /pair fallback

1 participant