Skip to content

fix(nip11): advertise auth_required: true to match actual enforcement#556

Merged
tlongwell-block merged 1 commit into
mainfrom
fix/nip11-auth-required-reflects-actual-enforcement
May 13, 2026
Merged

fix(nip11): advertise auth_required: true to match actual enforcement#556
tlongwell-block merged 1 commit into
mainfrom
fix/nip11-auth-required-reflects-actual-enforcement

Conversation

@tlongwell-block
Copy link
Copy Markdown
Collaborator

Summary

The relay's NIP-11 information document was advertising limitation.auth_required: false even though the relay's WebSocket protocol handlers (REQ, EVENT, COUNT) unconditionally require AuthState::Authenticated. This caused the desktop pairing flow to omit the /pair sidecar path from the QR code URL on NIP-43-enforcing relays, so phones could not pair.

The bug, traced end-to-end

  1. Relay (the lie): crates/sprout-relay/src/nip11.rs set auth_required: config.require_auth_token. But require_auth_token is the REST API token toggle — it has no effect on WebSocket protocol auth. The protocol handlers (handlers/req.rs, handlers/event.rs, handlers/count.rs) all unconditionally reject connections that are not AuthState::Authenticated, regardless of that flag. The NIP-11 doc has been inconsistent with the protocol since the initial commit (058c4b92); PR feat: relay membership with NIP-43 compliance #448 ("relay membership with NIP-43 compliance") added 43 to supported_nips and the self field but did not revisit auth_required.

  2. Desktop (the consumer): desktop/src-tauri/src/commands/pairing.rs::probe_relay_requires_auth reads limitation.auth_required to decide whether to append /pair to the relay URL embedded in the pairing QR. The /pair path is the unauthenticated NIP-AB sidecar that an unpaired phone needs to reach the desktop. With a false advertisement, the QR pointed at the bare relay URL and the phone could not connect (NIP-43 membership check fails).

  3. Empirical confirmation against wss://sprout.up.railway.app (production):

    • Anonymous REQ → AUTH challenge + CLOSED auth-required: not authenticated.
    • AUTH with freshly-generated keypair → OK <id> false "restricted: not a relay member" (from handle_auth in crates/sprout-relay/src/handlers/auth.rs).
    • The relay's NIP-11 doc still says "auth_required": false. That's what this PR fixes.

The fix

  • crates/sprout-relay/src/nip11.rs: extract relay_limitation() (the canonical RelayLimitation) so the advertised invariant is testable without going through Config::from_env(). Set auth_required: true. Rename RelayInfo::from_config(_unused_config, relay_pubkey) to RelayInfo::build(relay_pubkey)Config was no longer used. New unit test pins the invariant.
  • crates/sprout-relay/src/router.rs: update two callers to RelayInfo::build(...).
  • crates/sprout-relay/src/config.rs: correct stale doc on require_auth_token and the startup warning — they previously said "NIP-42 / unauthenticated connections," which is the exact confusion that produced this bug.
  • crates/sprout-test-client/tests/e2e_relay.rs: tighten the existing nip11 assertion from "is a bool" to "== true", with a comment naming the invariant.

Propagation to the desktop

No desktop changes are needed. Once a relay redeploys with this fix, probe_relay_requires_auth in desktop/src-tauri/src/commands/pairing.rs will see auth_required: true and append /pair to the QR URL automatically. The pairing flow then routes the phone through the existing sprout-pair-relay sidecar, which has been correctly configured and routed at the nginx layer all along.

Tests

  • New unit test: nip11::tests::auth_required_is_advertised_true (runs in cargo test -p sprout-relay).
  • Tightened e2e assertion in e2e_relay::test_nip11_relay_info (runs against a live relay).
  • All 147 sprout-relay unit tests pass. Clippy clean. Pre-commit hooks (rust-fmt, clippy, rust-tests, desktop-check, desktop-tauri-fmt, desktop-tauri-check, mobile-check, mobile-test, web-check) pass.

Reviewed with

Iterated with codex review until 10/10 on minimalism, elegance, and correctness.

The REQ, EVENT, and COUNT handlers in this relay unconditionally require
AuthState::Authenticated. The previous value (config.require_auth_token)
reflected the REST API token toggle, not WebSocket protocol auth, so
NIP-11 was lying about behaviour.

This breaks the desktop pairing flow: probe_relay_requires_auth in
desktop/src-tauri/src/commands/pairing.rs reads limitation.auth_required
to decide whether to route the phone's QR code URL through the
unauthenticated /pair sidecar. With the wrong NIP-11 value, the QR
pointed at the bare relay URL and the phone could not pair against a
NIP-43 enforcing relay.

Tighten the existing e2e nip11 test to pin the invariant: 'is a bool'
becomes 'is true', with a comment naming what enforces it.

Signed-off-by: Tyler Longwell <109685178+tlongwell-block@users.noreply.github.com>
@tlongwell-block tlongwell-block force-pushed the fix/nip11-auth-required-reflects-actual-enforcement branch from 6dced8b to 3f2ec07 Compare May 12, 2026 23:52
@tlongwell-block tlongwell-block merged commit cacfe6b into main May 13, 2026
15 checks passed
@tlongwell-block tlongwell-block deleted the fix/nip11-auth-required-reflects-actual-enforcement branch May 13, 2026 00:08
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.

1 participant