🐛 Problem
When a kind:9033 (RELAY_ADMIN_SET_WORKSPACE_PROFILE) event is rejected with "actor not authorized: must be admin or owner", the relay emits no server-side log identifying which pubkey was looked up or why get_relay_member returned None. The only signal is the client-facing ["OK", <id>, false, "invalid: actor not authorized: must be admin or owner"] NIP-01 response over the WebSocket.
This makes the failure completely invisible from the operator side. The kind:9033 path goes over WebSocket (not the HTTP bridge), so it never appears in the bridge INFO logs at all.
🔍 What Happened
During a live deployment of buzz 0.2.0, a relay admin attempt to upload the workspace/community icon consistently failed with "actor not authorized: must be admin or owner" despite the relay_members table containing rows for both the operator and user pubkeys with role owner.
Root cause: a manual DB repair job had inserted pubkeys as 66-char \x-prefixed strings (e.g. \x6abac6326580...) into the relay_members.pubkey TEXT column. The relay's get_relay_member queries with 64-char plain hex (as documented in relay_members.rs: "pubkey values are 64-char lowercase hex strings"), so the lookup returned None → sender_role = "" → auth denied.
# What was in the DB (wrong — 66 chars with \x prefix):
\x6abac6326580def897eb8d73903ae742f071603870020e65ccaf71edf697213e | owner
\x4a2efbc16eb5c5257d869412c0a60c15ec80f2a1df220917ed903759d6057514 | owner
# What the relay queries for (correct — 64 chars, no prefix):
6abac6326580def897eb8d73903ae742f071603870020e65ccaf71edf697213e | (not found → None)
has_admin_or_owner (no pubkey predicate) still returned true because the \x-prefixed rows exist → community_has_steward = true → the may_set_workspace_profile gate required admin/owner → sender_role = "" → denied.
The mismatch would have been immediately obvious with one debug! line.
🕵️ Relevant Code Paths
crates/buzz-relay/src/handlers/relay_admin.rs — execute_relay_admin_command, the kind:9033 branch
crates/buzz-db/src/relay_members.rs — get_relay_member (SQL: WHERE community_id = $1 AND pubkey = $2)
may_set_workspace_profile returns false when sender_role == "" and community_has_steward == true
✅ Suggested Fix
Add a debug! (or warn! since it results in a denial) log in execute_relay_admin_command after the get_relay_member call, before the may_set_workspace_profile gate:
let sender_member = state
.db
.get_relay_member(tenant.community(), &sender_hex)
.await
.map_err(|e| format!("database error: {e}"))?;
let sender_role = sender_member
.as_ref()
.map(|m| m.role.as_str())
.unwrap_or("");
// Suggested addition:
if sender_member.is_none() {
tracing::debug!(
pubkey = %sender_hex,
community = %tenant.community(),
kind = event.kind.as_u16(),
"relay_admin: no relay_member row found for sender — will be treated as no role"
);
}
This would have immediately surfaced the lookup key and confirmed the format mismatch without any DB spelunking.
🌍 Environment
buzz-relay 0.2.0 (image tag main, rolling)
- PostgreSQL 16 (Azure DB for PostgreSQL Flexible Server)
relay_members.pubkey column type: TEXT
require_relay_membership: false (open relay with steward detection via has_admin_or_owner)
🐛 Problem
When a
kind:9033(RELAY_ADMIN_SET_WORKSPACE_PROFILE) event is rejected with"actor not authorized: must be admin or owner", the relay emits no server-side log identifying which pubkey was looked up or whyget_relay_memberreturnedNone. The only signal is the client-facing["OK", <id>, false, "invalid: actor not authorized: must be admin or owner"]NIP-01 response over the WebSocket.This makes the failure completely invisible from the operator side. The
kind:9033path goes over WebSocket (not the HTTP bridge), so it never appears in the bridgeINFOlogs at all.🔍 What Happened
During a live deployment of
buzz0.2.0, a relay admin attempt to upload the workspace/community icon consistently failed with"actor not authorized: must be admin or owner"despite therelay_memberstable containing rows for both the operator and user pubkeys with roleowner.Root cause: a manual DB repair job had inserted pubkeys as 66-char
\x-prefixed strings (e.g.\x6abac6326580...) into therelay_members.pubkeyTEXT column. The relay'sget_relay_memberqueries with 64-char plain hex (as documented inrelay_members.rs: "pubkey values are 64-char lowercase hex strings"), so the lookup returnedNone→sender_role = ""→ auth denied.has_admin_or_owner(no pubkey predicate) still returnedtruebecause the\x-prefixed rows exist →community_has_steward = true→ themay_set_workspace_profilegate required admin/owner →sender_role = ""→ denied.The mismatch would have been immediately obvious with one
debug!line.🕵️ Relevant Code Paths
crates/buzz-relay/src/handlers/relay_admin.rs—execute_relay_admin_command, thekind:9033branchcrates/buzz-db/src/relay_members.rs—get_relay_member(SQL:WHERE community_id = $1 AND pubkey = $2)may_set_workspace_profilereturnsfalsewhensender_role == ""andcommunity_has_steward == true✅ Suggested Fix
Add a
debug!(orwarn!since it results in a denial) log inexecute_relay_admin_commandafter theget_relay_membercall, before themay_set_workspace_profilegate:This would have immediately surfaced the lookup key and confirmed the format mismatch without any DB spelunking.
🌍 Environment
buzz-relay0.2.0 (image tagmain, rolling)relay_members.pubkeycolumn type:TEXTrequire_relay_membership: false(open relay with steward detection viahas_admin_or_owner)