Role-Based Access Control for Rooms #47
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
We need a way for organizers to assign roles to users, and automatically authorize room access (at least) to those users. We could do this using a proprietary relay interface, or design a relay-level NIP for access control, maybe using UCANs. NIP 29 also has provisions for this, and we would probably want to add it to NIP 43 and 86 as well.
From bastiat
RBACto Role-Based Access Control@hodlbod Mind if i work on this issue?
Sure, this one is going to be a very high level of difficulty, with a first pass involving protocol work, maybe even a PR to the NIPs repo. Please go ahead and start by writing a complete proposal in this issue for how to implement this at the protocol level. Try not to scope creep too much, because I'm sure that will be a temptation for something like this. I look forward to seeing what you come up with!
Sure, thanks. Ill keep this on mind!
Hey @Khushvendra I've unassigned you so that others can jump in on this.
Hey @hodlbod! Sorry about that, have been occupied with some other stuff past couple of days (completely forgot about this one!). Will get to this asap!
@hodlbod I took a protocol-first pass that stays intentionally narrow, while still unblocking implementation.
RBAC Protocol Proposal (v1, minimal scope)
Problem we need to solve
We already have role labels in NIP-29, but we do not have interoperable semantics for:
That gap makes each relay/client invent its own RBAC model.
Design goals
Non-goals for v1
Proposed NIP-29 changes
1) Extend
kind:39003(group roles) with optional structured role metadataKeep current tag valid:
["role", "<role-id>", "<optional-description>"]Add optional tags in the same event:
["role-label", "<role-id>", "<display-name>"]["role-color", "<role-id>", "<#RRGGBB>"]["role-order", "<role-id>", "<integer>"](higher value = higher precedence)["role-hoist", "<role-id>", "1"](optional; for member-list separation behavior)This gives us Discord-like visual identity and deterministic sorting without breaking older parsers.
2) Clarify role assignment semantics on existing
kind:9000(put-user)Current format already supports roles in
ptag. We formalize behavior:9000for a pubkey in a group replaces that pubkey's effective role set for that group.9001removes membership and clears effective role state for that group.role-orderis the "primary role" for display and precedence checks.3) Add room role-gates on
kind:39000(group metadata)Add optional tags:
["access-role", "read", "<role-id>"]["access-role", "write", "<role-id>"]["access-role", "join", "<role-id>"]Semantics:
access-roletags for the same capability are OR.private/restricted/closedbehavior remains unchanged.This is the key piece that satisfies "assign roles, auto-authorize room access".
4) Precedence rule (minimal normative behavior)
When a moderation action targets another user, relay SHOULD reject actions from users whose highest role-order is lower than or equal to the target's highest role-order.
This captures the important Discord-style hierarchy behavior while staying simple.
Examples
kind:39003role catalogkind:39000room access by rolekind:9000assign roles to userBackward compatibility
role/ existing room metadata.NIP-43 and NIP-86 follow-up (small, separate PRs)
NIP-43 follow-up
28934/8000) is independent from group/room role authorization, which is evaluated per NIP-29 metadata.NIP-86 follow-up
Add optional group-scoped convenience methods for admin dashboards (relay can translate these into canonical NIP-29 events internally):
listgrouproles(group_id)setuserroles(group_id, pubkey, roles[])setgroupaccessroles(group_id, {read:[], write:[], join:[]})These are convenience APIs only; event-based NIP-29 remains source of truth.
Implementation plan (practical)
Phase 1 (spec PR)
Phase 2 (relay + Flotilla)
Phase 3 (optional)
If this direction looks right, my next step is to open a focused NIP-29 PR draft with exact normative wording for the new tags and validation rules, then implement relay/client support behind feature flags.
This is a great first pass. A few problems that need to be solved:
With that in mind, here are some ideas to improve this:
access-roleon39000torole-accesson39003. It doesn't make sense to spread roles across too many events.role-coloras a hue (0-255) which can be used in HSL or OKLCH color schemas.role-permissionwhich can be any privileged 9xxx kind (9000, 9001, 9002, 9005, 9009). This allows the group admins list to specify multiple roles (as defined in nip 29), each of which carry explicit permissions. This decouples abilities from roles, because you can have a role with no permissions.Want to make a second pass with this in mind? When opening PRs to NIP 29/43 we should separate them out to be as granular as we can.
@hodlbod Thanks for the detailed feedback. I made a second pass that incorporates all of this and keeps scope tight.
RBAC Protocol Proposal (v2, revised)
Core corrections from v1
Updated NIP-29 model
1) Keep roles centralized in kind 39003
kind 39003 remains the single role schema event for a room/group.
Additive tags (all optional):
["role", "", ""]
["role-label", "", ""]
["role-color", "", ""]
["role-order", "", ""] // UI sorting only
["role-permission", "", "<9xxx-kind>"]
["role-access", "", "read|write|join"]
Normative clarification:
2) Clarify role assignment on 9000/9001
3) Explicitly decouple “admin” from role names
The implicit “admin” interpretation in NIP-29 should be tightened:
4) Add role annotations to member/admin listings for UX
To support Discord-like cosmetic display and sorting:
Relay-level roles + inheritance without breaking NIP-29-only clients
Problem
We want optional server-level role definitions (NIP-43 side), but room authorization must still work for clients/relays that only implement NIP-29.
Rule
Room 39003 must remain fully authoritative for room validation.
If relay-level roles are inherited, relay materializes the resolved role set into room 39003 and validates against that resolved set.
That means:
NIP-43 alignment (separate PR series)
Mirror the same schema concepts at relay scope:
This keeps 29/43 conceptually consistent.
Validation behavior (minimal but strict)
Relay MUST reject:
Granular PR plan
NIP-29 PR 1: Role schema + explicit permissions
NIP-29 PR 2: Assignment and listings
NIP-29 PR 3: Inheritance materialization rule
NIP-43 PR 1: Relay role catalog/admin/member extensions
NIP-43 PR 2: 29/43 interoperability text
This keeps each PR reviewable and avoids a giant coupled spec diff.
If this direction is good, I will draft PR-ready normative text in this exact sequence (29 PR1 -> 29 PR2 -> 29 PR3 -> 43 PR1 -> 43 PR2).
Role-Based Access Controlto Role-Based Access Control for RoomsThis looks great. I think we should probably just do two PRs, one to nip 29 and one (after that one has been accepted/implemented) to nip 43. I've opened #192 for the NIP 43 part; let's just focus on NIP 29 here. Next step would be to open a PR on the nips repo, want to go ahead and do that?
Sure, works for me
@hodlbod Updated plan aligned with your guidance:
Sequencing
Scope for the NIP-29 PR only
Keep role schema centralized on kind 39003
Clarify authorization model
Tighten assignment/listing behavior
Compatibility rule
Explicit non-goals in this PR
Immediate next steps
If this looks right, Should i start with the implementation?
Yep, looks good!
@hodlbod Opened NIP-29 PR, scoped to this issue only. (https://github.com/nostr-protocol/nips/pull/2316)
(NIP-43 follow-up remains in #192)
Hey @hodlbod, I've got an implementation plan mapped out for the Flotilla side of NIP-29 RBAC, but I have a few quick questions regarding scope and
@welshmandependencies before the implementation part....Admin Role Management UI: Should we include an admin-facing "Manage Roles" UI (to create, edit, or delete roles/colors/permissions) in this first pass, or should we just display the existing roles read-only? Implementing the management UI would require building out
kind:9002(edit-metadata) or NIP-86 method flows for roles. My recommendation is to keep it read-only for v1, and handle the admin management UI in a follow-up PR.Welshman
ROOM_ROLESConstant: Does@welshman/utilcurrently export a constant forkind:39003? I seeROOM_ADMINS(39001),ROOM_MEMBERS(39002), andROOM_META(39000), but I didn't spot 39003. If it's not exported yet, I can defineROOM_ROLES = 39003locally in Flotilla for now.Member List Role Annotations: The NIP-29 updates allow p tags in
kind:39002(members list) to carry role names after the pubkey for cosmetic/UX purposes. Does the current WelshmangetPubkeyTagValues(or similar utility) preserve the extra positional tag values, or does it only extract index[1]? I think this affects how we'll parse the member roles from the event.lmk your thoughts on the UI scope, and then imo we can start implementing stuff!
getPubkeyTagValuesdoesn't keep the roles, you're right that we'll have to switch member lists to a list of structs instead.Khushvendra referenced this issue2026-04-20 21:46:16 +00:00
This is still going through some design iterations: https://github.com/nostr-protocol/nips/pull/2376/changes