block / block/buzz

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

Open
#4,982 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
32.7k
Forks
4.3k
Avg merge
1d 13h
Merged PRs (30d)
253

Description

## 🐛 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", , 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:

```rust
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`)

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.