Skip to content

feat(relay): add operator community provisioning#1657

Merged
baxen merged 3 commits into
mainfrom
mini/community-provisioning
Jul 8, 2026
Merged

feat(relay): add operator community provisioning#1657
baxen merged 3 commits into
mainfrom
mini/community-provisioning

Conversation

@baxen

@baxen baxen commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

Why

Enable kgoose or another deployment operator to provision and inspect Buzz communities without direct Postgres access, while preserving the relay's host-derived tenant boundary and keeping the Nostr event data plane unchanged.

What

  • Add POST /operator/communities as a standalone NIP-98-signed operator endpoint
  • Add GET /operator/communities?owner_pubkey=<hex> for kgoose's list-owned-communities read
  • Add GET /operator/communities/availability?host=<host> so the relay is the source of truth for host normalization and availability checks
  • Gate all operator endpoints behind RELAY_OPERATOR_PUBKEYS, fail closed by default, and hard-error on malformed configured pubkeys
  • Reuse the community provisioning handler for strict create-host validation, idempotent communities ensure/create, optional owner bootstrap, and availability host normalization
  • Document that initial_owner_pubkey on an existing community rotates the owner via the same bootstrap path as RELAY_OWNER_PUBKEY
  • Remove the event-kind/ingest approach from the draft: no kind 9037, no relay-membership bypass in /events, no ingest routing carve-out, no storage/fan-out

Risk Assessment

Medium — this adds a deployment-root authority surface. The blast radius is bounded by NIP-98 request signing, replay protection, explicit RELAY_OPERATOR_PUBKEYS allowlist, strict host/pubkey validation, and a no-op default config. Keeping this as a separate operator endpoint avoids special cases in the tenant-scoped event ingest path.

References

  • Validation: cargo fmt
  • Validation: cargo fmt --check
  • Validation: cargo check -p buzz-relay
  • Validation: cargo test -p buzz-core no_duplicate_kind_values --lib
  • Validation: cargo test -p buzz-relay community_provisioning --lib
  • Validation: cargo test -p buzz-relay relay_operator_pubkeys --lib
  • Validation: cargo test -p buzz-db list_communities_owned_by_returns_only_owner_rows --lib (ignored by default as Postgres-dependent)
  • Pre-commit hooks passed with Hermit activated: sadscan, rust-fmt, web-fix, desktop-tauri-fmt, desktop-fix, mobile-fix
  • Earlier push hooks passed with Hermit activated: check-push-org, desktop-test, mobile-test, desktop-tauri-test, rust-tests

Generated with Codex

@baxen baxen force-pushed the mini/community-provisioning branch from 4fecafd to 5301f3d Compare July 8, 2026 21:16
Co-authored-by: npub1qvn3cujt28pg06ehlstrxyz6ayzp06t4uc7r566vxwwgrv24hglq9zju0n <03271c724b51c287eb37fc1633105ae90417e975e63c3a6b4c339c81b155ba3e@sprout-oss.stage.blox.sqprod.co>

Signed-off-by: npub1qvn3cujt28pg06ehlstrxyz6ayzp06t4uc7r566vxwwgrv24hglq9zju0n <03271c724b51c287eb37fc1633105ae90417e975e63c3a6b4c339c81b155ba3e@sprout-oss.stage.blox.sqprod.co>
@baxen baxen force-pushed the mini/community-provisioning branch from 5301f3d to de5fd17 Compare July 8, 2026 21:41
@baxen baxen marked this pull request as ready for review July 8, 2026 21:43

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: de5fd17d24

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

"POST",
"/operator/communities",
None,
Some(&body),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require a signed payload for provisioning bodies

When an operator client signs this POST without a NIP-98 payload tag, verify_bridge_auth still accepts the auth event and only binds the URL/method, leaving the JSON body (host and initial_owner_pubkey) unsigned. In that case, anyone who obtains a fresh Authorization: Nostr ... header can race the legitimate request with a different body and create or rotate a community before the replay guard records the event; the operator provisioning endpoint should reject body-bearing requests unless the auth event includes a matching payload hash.

Useful? React with 👍 / 👎.

@kalvinnchau kalvinnchau left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 found three issues worth fixing before merge:

  1. high: require signed payloads for provisioning bodies

    POST /operator/communities accepts NIP-98 auth without requiring a payload tag. In that case the auth event binds only URL + method, leaving host and initial_owner_pubkey unsigned. Anyone who gets a fresh Authorization: Nostr ... header can race it with a different body and create or rotate a community before the replay guard records the event. Please reject body-bearing provisioning requests unless the NIP-98 event includes a matching payload hash.

  2. medium: validate community hosts as real authorities before storing

    validate_host checks normalization and a few forbidden characters, but still accepts syntactically invalid authorities like :, malformed bracketed IPv6, example..com, labels with underscores, or overlong labels. Those can be inserted into communities.host even though they may never correspond to a valid HTTP Host authority. Please parse/validate as host[:port] and store only canonical normalized authorities.

  3. medium: avoid reporting created after a racing upsert conflict

    Provisioning computes existed with a separate lookup before ensure_configured_community. Two concurrent operator requests can both observe existed = false; one inserts, the other hits the upsert conflict but still returns status: "created". Please make the DB helper return whether the insert actually won, or do the check+insert in one transaction/CTE.

Co-authored-by: npub1qvn3cujt28pg06ehlstrxyz6ayzp06t4uc7r566vxwwgrv24hglq9zju0n <03271c724b51c287eb37fc1633105ae90417e975e63c3a6b4c339c81b155ba3e@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: npub1qvn3cujt28pg06ehlstrxyz6ayzp06t4uc7r566vxwwgrv24hglq9zju0n <03271c724b51c287eb37fc1633105ae90417e975e63c3a6b4c339c81b155ba3e@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1qvn3cujt28pg06ehlstrxyz6ayzp06t4uc7r566vxwwgrv24hglq9zju0n <03271c724b51c287eb37fc1633105ae90417e975e63c3a6b4c339c81b155ba3e@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: npub1qvn3cujt28pg06ehlstrxyz6ayzp06t4uc7r566vxwwgrv24hglq9zju0n <03271c724b51c287eb37fc1633105ae90417e975e63c3a6b4c339c81b155ba3e@sprout-oss.stage.blox.sqprod.co>
@baxen baxen merged commit e0f76b0 into main Jul 8, 2026
29 checks passed
@baxen baxen deleted the mini/community-provisioning branch July 8, 2026 23:56
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.

2 participants