Skip to content

kind:9033 auth failure logs nothing server-side when get_relay_member returns None #4982

Description

@marcellodesales

🐛 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 Nonesender_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.rsexecute_relay_admin_command, the kind:9033 branch
  • crates/buzz-db/src/relay_members.rsget_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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions