Media GET requires relay membership; the community owner is not a member by default and gets an opaque 401
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
On a self-hosted relay, every image failed to load in the mobile app — avatars, message attachments, everything. Fetching a media URL directly returned:
```
HTTP 401 {"error":"authentication failed"}
```
The cause turned out to be relay membership. `authenticate_media_read` in `crates/buzz-relay/src/api/media.rs` runs three checks in sequence:
```rust
let tenant = bind_media_read_tenant(state, headers).await?;
let auth_event = extract_blossom_auth(headers)?;
verify_blossom_get_auth(&auth_event, sha256, Some(tenant.host()), 3600)?;
enforce_relay_membership(state, tenant.community(), auth_event.pubkey.as_bytes(), auth_tag).await
.map_err(|_| MediaError::RelayMembershipRequired)?;
```
My account was the **community owner** — I created the community, I own every channel — but I had never been added to the relay membership roster, because nothing in the onboarding flow does that. Adding myself with `buzz-admin add-member` fixed it immediately and every image appeared.
Two things made this hard to diagnose.
First, `MediaError::RelayMembershipRequired` surfaces to the client as a generic `401 authentication failed`, indistinguishable from a signature problem. I spent a long time checking Blossom signing before suspecting membership.
Second, being the community owner not implying relay membership is surprising. These are separate concepts in the code and that is defensible, but from an operator's seat "I own this community and cannot read its media" reads as a bug.
**Suggestions**
1. Distinguish the membership rejection from the auth-signature rejection in the response — even a distinct error code in the JSON body would be enough. The generic-404-for-unmapped-hosts reasoning does not obviously apply here, since the caller has already proven key ownership through Blossom auth.
2. Consider adding the community creator to the relay roster during onboarding, or documenting that this is a required separate step for self-hosted deployments.
**Environment**: relay `ghcr.io/block/buzz:main`, self-hosted via `deploy/compose`, `BUZZ_REQUIRE_RELAY_MEMBERSHIP` at its default.
Contributor guide
Assessment
This issue has not been assessed yet.