NIP-98 auth rejects valid RELAY_URL when it includes a non-default port (self-hosted, WSS on non-443)
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
Body
Summary
When RELAY_URL is configured with a non-default port (e.g. wss://example.com:3443), the relay correctly derives and registers the deployment community using the full authority including the port (via relay_url_authority / normalize_host in crates/buzz-relay/src/main.rs). However, NIP-98 HTTP Auth verification for the /query bridge endpoint appears to validate the request URL against a different normalization of RELAY_URL that drops the port and/or forces a fixed scheme, causing every authenticated request to fail with a URL mismatch — even though the client is using the exact RELAY_URL the deployment was configured with.
This makes it impossible to self-host a Buzz relay behind a reverse proxy on any port other than the implicit default for the scheme (443 for wss/https, 80 for ws/http).
Environment
Relay: self-hosted, ghcr.io/block/buzz:main, deployed via deploy/compose/compose.yml
Reverse proxy: Nginx, terminating TLS (Let's Encrypt), forwarding WebSocket upgrade to the relay container
Desktop: v0.5.2
RELAY_URL=wss://example.com:3443 (a port other than 443, chosen because port 443 was already in use by another site sharing the same domain)
Reproduction
Deploy relay with RELAY_URL=wss://your-domain.com:3443 (any non-default port for the scheme).
Confirm the deployment community is registered with the port included:
INFO Deployment community ensured host="your-domain.com:3443" community=""
Point Buzz Desktop's community Relay URL to wss://your-domain.com:3443, matching exactly.
Desktop connects (WebSocket upgrade succeeds, 101 Switching Protocols), then attempts NIP-42 auth / NIP-98 bridge requests.
Observe:
relay returned 401 Unauthorized: NIP-98: NIP-98 HTTP Auth verification failed:
URL mismatch: event has `https://your-domain.com:3443/query`,
expected `https://your-domain.com/query`
The "expected" URL in the error has the port stripped, despite RELAY_URL (used successfully for community host derivation) including it.
Expected behavior
NIP-98 URL validation should use the same host/authority normalization as community derivation (relay_url_authority / normalize_host), so that a RELAY_URL with an explicit non-default port is honored consistently across both code paths.
Suspected cause
Two independent normalization paths for the same RELAY_URL config value:
Community/tenant resolution (crates/buzz-relay/src/tenant.rs, relay_url_authority) — preserves the port.
NIP-98 HTTP Auth verification (bridge /query endpoint) — appears to reconstruct the "expected" URL using a default scheme/port instead of the configured RELAY_URL authority verbatim.
Additional context
This was discovered while trying to run mobile pairing (/pair, NIP-AB) over a dedicated HTTPS port on a self-hosted relay that shares its domain with another web application on port 443. Related regression also observed independently: [#3514](https://github.com/block/buzz/issues/3514) (Mobile 0.4.12 does not decode Desktop 0.5.0 styled pairing QR) — not the same bug, but encountered in the same troubleshooting session and may be worth cross-referencing if investigating mobile pairing reliability on self-hosted deployments generally.
Happy to provide relay logs, full Nginx config, or test further changes if useful for debugging.
Contributor guide
Research direction
Start with crates/buzz-relay/src/main.rs and crates/buzz-relay/src/tenant.rs, especially relay_url_authority and normalize_host, then locate the /query bridge's NIP-98 URL verification. Reproduce with RELAY_URL=wss://your-domain.com:3443 and compare the expected URL with the request URL; done means authenticated requests no longer lose the explicit port and community derivation remains consistent.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- nginx, rust
- Domain
- authentication, backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100