feat: bitwise RBAC engine with channel-level permission overrides

Replace string-based role checks (role === 'admin') with a bitwise BigInt
permission system. Adds computePermissions() resolution engine following
Discord's model: @everyone base → role union → admin shortcut → channel
overrides (role deny/allow → member deny/allow). Ready payload now filters
channels by VIEW_CHANNEL and attaches per-user myPermissions to each
server and channel. Includes channel_overrides table, @everyone role
auto-creation, migration for existing servers, and override CRUD API.
This commit is contained in:
Jannis Braun
2026-02-24 05:08:59 +01:00
parent 024833c470
commit 8030c89c6c
19 changed files with 568 additions and 93 deletions
@@ -11,6 +11,7 @@ import { Avatar } from '../ui/Avatar';
import { wsSend } from '../../hooks/useWebSocket';
import { getActiveRoom } from '../../hooks/useLiveKit';
import { AudioManager } from '../../audio/AudioManager';
import { hasPermissionBit, PermissionBits } from '../../utils/permissions';
export function ChannelSidebar() {
const servers = useServerStore((s) => s.servers);
@@ -65,9 +66,10 @@ export function ChannelSidebar() {
}
};
const serverPermissions = useServerStore((s) => s.serverPermissions);
const server = servers.find(s => s.id === currentServerId);
const currentMember = members.find(m => m.userId === user?.id);
const isAdminUser = currentMember?.role === 'admin' || currentMember?.role === 'owner';
const myServerPerms = currentServerId ? serverPermissions.get(currentServerId) : undefined;
const canManageChannels = hasPermissionBit(myServerPerms, PermissionBits.MANAGE_CHANNELS);
const textChannels = channels.filter(c => c.type === 'text');
const voiceChannels = channels.filter(c => c.type === 'voice' || c.type === 'video');
@@ -306,7 +308,7 @@ export function ChannelSidebar() {
</svg>
<span className="text-[12px] font-bold uppercase tracking-wider">Text Channels</span>
</div>
{isAdminUser && (
{canManageChannels && (
<button
onClick={(e) => {
e.stopPropagation();
@@ -358,7 +360,7 @@ export function ChannelSidebar() {
</svg>
<span className="text-[12px] font-bold uppercase tracking-wider">Voice Channels</span>
</div>
{isAdminUser && (
{canManageChannels && (
<button
onClick={(e) => {
e.stopPropagation();