type:research: Public Access to Channels (No Auth) #63

Open
opened 2026-04-26 16:46:19 +00:00 by icub3d · 0 comments
Owner

Migrated from GitHub issue icub3d/decentcom#89
Original Author: @icub3d
Original Date: 2026-04-18T15:00:48Z


Research: Public Access to Channels

Overview

Design a mechanism to allow unauthenticated (no public key/session) read access to specific "public" channels within a decentcom server. Writes always require authentication.

Background

Currently, the API is strictly protected by the MemberUser extractor which requires a valid session and server membership. However, some servers might want "public announcement" channels that are visible to anyone with the server address, even without joining or providing a public key.

Design Direction

Read-only for public, always

Posting requires a stable identity for the rest of the model to hold together — rate limits, bans, audit log, and MENTION_EVERYONE all assume a user_id. An unauthenticated writer has none of that, and reinventing IP-based identity fights the Ed25519 design. Every use case collapses to read-only public access:

  • "Admin-only announcements" = channel where @everyone lacks SEND_MESSAGES.
  • "Members post, public reads" = same channel with READ_MESSAGES granted to a public viewer.

The only new capability needed is: "can an unauthenticated request read this channel?"

Fit into the existing permission model

@everyone + per-channel allow/deny overrides already exist (server/src/permissions.rs). Introduce a built-in pseudo-role @public that is not implied by @everyone (everyone = authenticated members; public = not-even-a-member). Admins grant @public the READ_MESSAGES permission via the normal channel override UI on whichever channels they want public.

HTTP-only, separate route prefix

Public access is exposed via a /public/... HTTP GET prefix only. No WebSocket/gateway support in v1 — announcement use cases don't need realtime, and unauthenticated socket lifecycle is a separate problem. The separate prefix lets operators front these routes with a CDN or external rate limiter/cache without touching authenticated routes.

API / Interface Changes

  • New OptionalAuthUser extractor returning Option<AuthUser>.
  • New /public/channels/:id and /public/channels/:id/messages GET routes.
  • When the extractor yields None, effective_permissions is computed against @public alone with channel overrides applied. GET endpoints check READ_MESSAGES; all other endpoints still require MemberUser.
  • New built-in @public role seeded at server init, not assigned to any member, not implied by @everyone.

Component Changes

  • server/src/permissions.rs — add @public role handling, extend effective_permissions / compute_base_permissions to work with an optional user.
  • server/src/auth/middleware.rs — add OptionalAuthUser extractor.
  • server/src/channels/handlers.rs, server/src/messages/handlers.rs — add public GET handlers mounted under /public.
  • server/src/config.rs — add public_access_enabled server-level kill switch.
  • Storage/seed — ensure @public role exists.

Task List

  • Draft docs/design/public-access.md capturing this design.
  • Seed the built-in @public role alongside @everyone.
  • Implement OptionalAuthUser extractor.
  • Extend permission computation to accept an optional user and resolve against @public when absent.
  • Add /public/ router with GET message/channel endpoints.
  • Add public_access_enabled server config flag that short-circuits all /public/ routes when disabled.
  • Admin UI / API to toggle @public channel overrides (can reuse existing channel override flow).

Test List

  • Unauthenticated GET on a channel where @public has READ_MESSAGES returns messages.
  • Unauthenticated GET on a channel where @public has no override returns 403.
  • Unauthenticated POST / write operations are rejected regardless of channel state.
  • Public endpoints return 404/disabled when public_access_enabled = false.
  • @public grants do not leak into @everyone permission computation for authenticated members.
  • Public responses expose only pubkey + display name for message authors (no email, presence, or role membership).

Open Questions

  • What exactly does a public viewer see of message authors? Proposed: pubkey + display name only; no email, no presence, no role list. Pin down in the design doc.
  • Should public GETs include pagination/limits tighter than authenticated reads (to reduce scraping cost)?
  • Abuse / rate limiting — handled externally via CDN / reverse proxy in front of /public/. Document the recommended deployment pattern.
**Migrated from GitHub issue icub3d/decentcom#89** **Original Author:** @icub3d **Original Date:** 2026-04-18T15:00:48Z --- # Research: Public Access to Channels ## Overview Design a mechanism to allow unauthenticated (no public key/session) read access to specific "public" channels within a decentcom server. Writes always require authentication. ## Background Currently, the API is strictly protected by the `MemberUser` extractor which requires a valid session and server membership. However, some servers might want "public announcement" channels that are visible to anyone with the server address, even without joining or providing a public key. ## Design Direction ### Read-only for public, always Posting requires a stable identity for the rest of the model to hold together — rate limits, bans, audit log, and `MENTION_EVERYONE` all assume a `user_id`. An unauthenticated writer has none of that, and reinventing IP-based identity fights the Ed25519 design. Every use case collapses to read-only public access: - "Admin-only announcements" = channel where `@everyone` lacks `SEND_MESSAGES`. - "Members post, public reads" = same channel with `READ_MESSAGES` granted to a public viewer. The only new capability needed is: *"can an unauthenticated request read this channel?"* ### Fit into the existing permission model `@everyone` + per-channel allow/deny overrides already exist (`server/src/permissions.rs`). Introduce a built-in pseudo-role `@public` that is **not** implied by `@everyone` (everyone = authenticated members; public = not-even-a-member). Admins grant `@public` the `READ_MESSAGES` permission via the normal channel override UI on whichever channels they want public. ### HTTP-only, separate route prefix Public access is exposed via a `/public/...` HTTP GET prefix only. No WebSocket/gateway support in v1 — announcement use cases don't need realtime, and unauthenticated socket lifecycle is a separate problem. The separate prefix lets operators front these routes with a CDN or external rate limiter/cache without touching authenticated routes. ## API / Interface Changes - New `OptionalAuthUser` extractor returning `Option<AuthUser>`. - New `/public/channels/:id` and `/public/channels/:id/messages` GET routes. - When the extractor yields `None`, `effective_permissions` is computed against `@public` alone with channel overrides applied. GET endpoints check `READ_MESSAGES`; all other endpoints still require `MemberUser`. - New built-in `@public` role seeded at server init, not assigned to any member, not implied by `@everyone`. ## Component Changes - `server/src/permissions.rs` — add `@public` role handling, extend `effective_permissions` / `compute_base_permissions` to work with an optional user. - `server/src/auth/middleware.rs` — add `OptionalAuthUser` extractor. - `server/src/channels/handlers.rs`, `server/src/messages/handlers.rs` — add public GET handlers mounted under `/public`. - `server/src/config.rs` — add `public_access_enabled` server-level kill switch. - Storage/seed — ensure `@public` role exists. ## Task List - [ ] Draft `docs/design/public-access.md` capturing this design. - [ ] Seed the built-in `@public` role alongside `@everyone`. - [ ] Implement `OptionalAuthUser` extractor. - [ ] Extend permission computation to accept an optional user and resolve against `@public` when absent. - [ ] Add `/public/` router with GET message/channel endpoints. - [ ] Add `public_access_enabled` server config flag that short-circuits all `/public/` routes when disabled. - [ ] Admin UI / API to toggle `@public` channel overrides (can reuse existing channel override flow). ## Test List - [ ] Unauthenticated GET on a channel where `@public` has `READ_MESSAGES` returns messages. - [ ] Unauthenticated GET on a channel where `@public` has no override returns 403. - [ ] Unauthenticated POST / write operations are rejected regardless of channel state. - [ ] Public endpoints return 404/disabled when `public_access_enabled = false`. - [ ] `@public` grants do not leak into `@everyone` permission computation for authenticated members. - [ ] Public responses expose only pubkey + display name for message authors (no email, presence, or role membership). ## Open Questions - What exactly does a public viewer see of message authors? Proposed: pubkey + display name only; no email, no presence, no role list. Pin down in the design doc. - Should public GETs include pagination/limits tighter than authenticated reads (to reduce scraping cost)? - Abuse / rate limiting — handled externally via CDN / reverse proxy in front of `/public/`. Document the recommended deployment pattern.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
icub3d/decentcom#63
No description provided.