Files
amethyst/quartz-relay/config.example.toml
T
Claude 7c7908d373 feat(relay): NIP-86 relay management API
Implements the operator-facing JSON-RPC admin protocol on the relay
side, layered on top of the existing NIP-98 HTTP-Auth + NIP-86 wire
types in :quartz. Same path as the NIP-01 WebSocket and NIP-11 GET —
HTTP POST with Content-Type application/nostr+json+rpc, gated by a
NIP-98 Authorization header signed by an operator-listed pubkey.

Architecture:
  - BanStore           — concurrent in-memory state for runtime
                         pubkey/event/kind ban + allow lists.
  - DynamicBanPolicy   — IRelayPolicy that consults BanStore on every
                         EVENT; auto-prepended to every Relay's policy
                         stack so admin actions take effect without a
                         restart.
  - Nip98AuthVerifier  — parses `Authorization: Nostr <base64-event>`,
                         verifies kind 27235 + Schnorr sig + ±60 s
                         clock skew + method/url/payload-hash match.
  - Nip86Server        — JSON-RPC dispatcher. Mutates BanStore for
                         ban/allow/list methods, mutates the live
                         RelayInfo doc for changerelayname/desc/icon
                         (Relay.info is now @Volatile var, mutation
                         through Relay.updateInfo).
  - LocalRelayServer   — adds POST handler at the relay path:
                         403 if no admin pubkeys configured,
                         401 if NIP-98 missing/invalid,
                         403 if signer's pubkey isn't on the admin list,
                         200 with Nip86Response otherwise.
  - RelayConfig.AdminSection — `[admin].pubkeys = [...]` config key.
  - RelayInfo.default() now advertises NIP-86 alongside 1/9/11/40/42/45/50/62.

Methods implemented:
  supportedmethods, banpubkey, unbanpubkey, listbannedpubkeys,
  allowpubkey, unallowpubkey, listallowedpubkeys,
  banevent (also deletes from store), allowevent, listbannedevents,
  allowkind, disallowkind, listallowedkinds,
  changerelayname, changerelaydescription, changerelayicon.

Tests (28 new):
  - BanStoreTest (6)            — ban/allow round-trips, case-insensitive
                                  pubkey match, kind allow/deny precedence,
                                  audit-trail listing.
  - Nip86ServerTest (8)         — every method's accept/reject path.
  - Nip98AuthVerifierTest (8)   — happy path, missing/wrong scheme,
                                  url/method/payload-hash mismatch,
                                  stale created_at, wrong event kind.
  - Nip86EndToEndTest (6)       — real HTTP POST through LocalRelayServer:
                                  supportedmethods returns 200,
                                  outsider returns 403, no auth header
                                  returns 401 + WWW-Authenticate, banpubkey
                                  blocks subsequent EVENT publish over WS,
                                  changerelayname flows to NIP-11 GET,
                                  admin endpoint is disabled when no
                                  pubkeys configured.

Total :quartz-relay tests: 87, 0 failures.
2026-05-07 02:55:47 +00:00

71 lines
2.5 KiB
TOML

# Example config for quartz-relay. Section layout mirrors
# nostr-rs-relay's config.toml so existing operators can port across.
#
# Run with:
# ./gradlew :quartz-relay:run --args="--config /etc/quartz-relay.toml"
#
# CLI flags override individual values: e.g. `--port 8888` wins over
# `[network].port`.
[info]
# The wss:// URL clients use to reach this relay (mandatory for NIP-42
# AUTH challenges). If not set, the relay synthesises one from the
# [network] section.
relay_url = "wss://relay.example.com/"
name = "Example Quartz Relay"
description = "A quartz-relay deployment."
contact = "admin@example.com"
# Operator pubkey (NIP-11). Optional.
# pubkey = "..."
# Override the supported NIPs advertised on the NIP-11 endpoint. If
# omitted, the relay advertises the NIPs it actually implements.
# supported_nips = [1, 9, 11, 40, 42, 45, 50, 62]
[network]
host = "0.0.0.0"
port = 7447
path = "/"
[database]
# True keeps an in-memory SQLite db (events vanish on restart). Useful
# for tests; set false + `file = "..."` for persistent storage.
in_memory = false
file = "/var/lib/quartz-relay/events.db"
[options]
# Drop events whose Schnorr signature does not verify. Strongly
# recommended for any relay accepting traffic from real clients.
# Verify Schnorr signatures on every EVENT. Default: true. Disable
# only for trusted-input scenarios (test fixtures, mirror replays).
# verify_signatures = true
# Require clients to NIP-42 AUTH before REQ/EVENT/COUNT.
require_auth = false
# Reject events whose `created_at` is more than this many seconds in
# the future. Enforced by RejectFutureEventsPolicy.
# reject_future_seconds = 1800
[limits]
# Maximum WebSocket frame size. Frames larger than this are dropped at
# the WS layer. (max_ws_message_bytes maps to the same setting since
# Ktor's WebSockets plugin only exposes per-frame caps.)
# max_ws_message_bytes = 1048576
# max_ws_frame_bytes = 1048576
[authorization]
# Allow / deny lists. Allow is a permissive ceiling; deny still
# removes specific entries inside it. Enforced by Pubkey/KindAllowDenyPolicy.
# pubkey_whitelist = ["abcdef...64hex..."]
# pubkey_blacklist = []
# kind_whitelist = [0, 1, 3, 7, 1059, 30023]
# kind_blacklist = [4]
[admin]
# NIP-86 relay management API. When `pubkeys` is non-empty, the relay
# accepts HTTP POST application/nostr+json+rpc on the same URL,
# authenticated with NIP-98 HTTP-Auth. Only events signed by one of
# the listed pubkeys can run admin RPCs (banpubkey / banevent /
# changerelayname / …). Empty (the default) disables the endpoint.
# pubkeys = ["abcdef...64hex..."]