Files
backspace/docs/systems/permissions.md
T
Jannis Braun a3a7527c9e chore: add system docs, specs, and misc updates from other sessions
- Add complete docs/systems/ reference (18 system docs)
- Add federation relay status doc and prior spec/plan docs
- Remove superseded docs/federation-dm-s2s.md (replaced by docs/systems/federation.md)
- CLAUDE.md updates
- Minor fixes in social.ts, types.ts, AddDmMemberModal, NewDmModal, UserSettings
2026-03-31 03:40:34 +02:00

3.6 KiB

Permission System

Source files:

  • packages/shared/src/permissions.ts — Bit definitions, constants
  • packages/server/src/utils/permissions.ts — Server-side resolution
  • packages/web/src/utils/permissions.ts — Client-side helpers

Storage: Bigint decimal strings in SQLite TEXT columns (bigint not JSON-safe).


Permission Bits

Bit Name Description
0 ADMINISTRATOR Full access, bypasses all checks
1 VIEW_CHANNEL See channel, read messages
2 MANAGE_CHANNELS Create/edit/delete channels + categories
3 MANAGE_ROLES Create/edit/delete roles, assign roles
4 MANAGE_SPACE Edit space settings, manage join requests
5 CREATE_INVITE Generate invite codes
6 KICK_MEMBERS Remove members
7 BAN_MEMBERS Ban members
10 SEND_MESSAGES Post in text channels
11 MANAGE_MESSAGES Delete others' messages
12 ATTACH_FILES Upload files
13 READ_MESSAGE_HISTORY View message history
14 ADD_REACTIONS Add emoji reactions
20 CONNECT Join voice channels
21 SPEAK Transmit audio
22 MUTE_MEMBERS Space-mute others
23 DEAFEN_MEMBERS Space-deafen others
24 MOVE_MEMBERS Move between voice channels
25 STREAM Screen share
26 DISCONNECT_MEMBERS Disconnect from voice

Default @everyone: VIEW_CHANNEL, SEND_MESSAGES, CREATE_INVITE, CONNECT, SPEAK, ATTACH_FILES, READ_MESSAGE_HISTORY, ADD_REACTIONS, STREAM


Resolution Algorithm

computePermissions(userId, spaceId, channelId?) → bigint

Step 1: Owner/Admin Check

  • Space owner OR instance admin (isAdmin === 1) → return ALL_PERMISSIONS

Step 2: Compute Base (space-level)

  • Start with @everyone role permissions (role where id === spaceId)
  • OR together all permissions from user's assigned roles
  • If ADMINISTRATOR bit set → return ALL_PERMISSIONS

Step 3: Apply Overrides (if channelId provided)

Three tiers, each applied category-first then channel-second:

Tier 1 — @everyone override (targetType='role', targetId=spaceId):

if categoryOverride: base = (base & ~deny) | allow
if channelOverride:  base = (base & ~deny) | allow

Tier 2 — Role overrides (combined across all assigned roles):

catAllow = 0, catDeny = 0
for each role: catAllow |= roleOverride.allow; catDeny |= roleOverride.deny
base = (base & ~catDeny) | catAllow

chanAllow = 0, chanDeny = 0
for each role: chanAllow |= roleOverride.allow; chanDeny |= roleOverride.deny
base = (base & ~chanDeny) | chanAllow

Tier 3 — Member override (targetType='member', targetId=userId):

if categoryOverride: base = (base & ~deny) | allow
if channelOverride:  base = (base & ~deny) | allow

Key rule: Channel bits always win — applied after category, overwriting conflicting bits. Deny applied first (clears bits), then allow (sets bits).


Helper Functions

Function Purpose
hasPermissionBit(perms, bit) Check if bit is set; true if ADMINISTRATOR
permissionsToString(perms) Bigint → decimal string for JSON
stringToPermissions(str) Decimal string → bigint (supports legacy JSON array format)
computePermissions(userId, spaceId, channelId?) Full resolution algorithm
computeCategoryPermissions(userId, spaceId, categoryId) Stops at category level (no channel overrides)
hasPermission(userId, spaceId, permission, channelId?) Boolean wrapper
getMember/isMember/isSpaceOwner Membership checks
isDmMember/isBanned DM/ban checks
getChannelSpaceId(channelId) Resolve channel's space