From 018828575c4cab7ce987b3eabff7acb76079fb88 Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Mon, 9 Mar 2026 23:01:27 +0100 Subject: [PATCH] fix: handle legacy JSON array permissions and improve admin toggle UX - Add migration to convert legacy JSON array permissions to decimal strings - Add runtime fallback in stringToPermissions for legacy format - Show all permission toggles as enabled (dimmed) when Administrator is on --- packages/server/src/db/migrate.ts | 33 +++++++++++++++++++ packages/shared/src/permissions.ts | 12 +++++++ .../modals/spaceSettingsPanels/RolesPanel.tsx | 20 ++++++----- 3 files changed, 57 insertions(+), 8 deletions(-) diff --git a/packages/server/src/db/migrate.ts b/packages/server/src/db/migrate.ts index 34fa31a7..11e1104f 100644 --- a/packages/server/src/db/migrate.ts +++ b/packages/server/src/db/migrate.ts @@ -154,6 +154,9 @@ export function runMigrations(db: Database.Database): void { ); `); + // ─── Legacy permissions: convert JSON arrays to decimal strings ─────────── + migrateLegacyPermissions(db); + // ─── RBAC Migration: Ensure @everyone roles exist for all spaces ───────── migrateEveryoneRoles(db); @@ -175,6 +178,36 @@ export function runMigrations(db: Database.Database): void { console.log('Migrations complete.'); } +/** Convert legacy JSON array permissions (e.g. '["VIEW_CHANNEL"]') to decimal strings */ +function migrateLegacyPermissions(db: Database.Database): void { + const roles = db.prepare('SELECT id, permissions FROM roles WHERE permissions IS NOT NULL').all() as { id: string; permissions: string }[]; + const update = db.prepare('UPDATE roles SET permissions = ? WHERE id = ?'); + + for (const role of roles) { + // Skip if already a valid decimal string + try { BigInt(role.permissions); continue; } catch {} + + // Try legacy JSON array + try { + const parsed = JSON.parse(role.permissions); + if (Array.isArray(parsed)) { + let result = 0n; + for (const key of parsed) { + const bit = PermissionBits[key as keyof typeof PermissionBits]; + if (bit !== undefined) result |= bit; + } + update.run(result.toString(), role.id); + console.log(`Migrating: Converted legacy permissions for role ${role.id}`); + continue; + } + } catch { /* not JSON either */ } + + // Unrecognized format — set to 0 + update.run('0', role.id); + console.log(`Migrating: Reset unrecognized permissions for role ${role.id}`); + } +} + /** Ensure the single-row instance_settings row exists */ function migrateInstanceSettings(db: Database.Database): void { const row = db.prepare('SELECT id FROM instance_settings WHERE id = 1').get(); diff --git a/packages/shared/src/permissions.ts b/packages/shared/src/permissions.ts index 72147018..f444612b 100644 --- a/packages/shared/src/permissions.ts +++ b/packages/shared/src/permissions.ts @@ -63,6 +63,18 @@ export function stringToPermissions(str: string | undefined | null): bigint { try { return BigInt(str); } catch { + // Fallback: legacy JSON array format (e.g. '["VIEW_CHANNEL","SEND_MESSAGES"]') + try { + const parsed = JSON.parse(str); + if (Array.isArray(parsed)) { + let result = 0n; + for (const key of parsed) { + const bit = PermissionBits[key as keyof typeof PermissionBits]; + if (bit !== undefined) result |= bit; + } + return result; + } + } catch { /* not JSON either */ } return 0n; } } diff --git a/packages/web/src/components/modals/spaceSettingsPanels/RolesPanel.tsx b/packages/web/src/components/modals/spaceSettingsPanels/RolesPanel.tsx index 234e1e05..bd1455bc 100644 --- a/packages/web/src/components/modals/spaceSettingsPanels/RolesPanel.tsx +++ b/packages/web/src/components/modals/spaceSettingsPanels/RolesPanel.tsx @@ -330,24 +330,28 @@ function RoleEditView({ role, spaceId, onBack, onDeleted }: RoleEditViewProps) {