Skip to content

Commit 197dd8b

Browse files
author
Lasim
committed
feat: centralize role permissions management and synchronize with database
1 parent 83579a4 commit 197dd8b

File tree

7 files changed

+291
-94
lines changed

7 files changed

+291
-94
lines changed

services/backend/drizzle/migrations_sqlite/0003_huge_prism.sql

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ CREATE UNIQUE INDEX `roles_name_unique` ON `roles` (`name`);
1313
ALTER TABLE `authUser` ADD `role_id` text REFERENCES roles(id);
1414
--> statement-breakpoint
1515

16-
-- Insert default roles
16+
-- Insert default roles (permissions will be synced by RoleSyncService on server startup)
1717
INSERT INTO `roles` (`id`, `name`, `description`, `permissions`, `is_system_role`, `created_at`, `updated_at`) VALUES
18-
('global_admin', 'Global Administrator', 'Full system access with user management capabilities', '["users.list","users.view","users.edit","users.delete","users.create","roles.manage","system.admin","settings.view","settings.edit","settings.delete","teams.create","teams.view","teams.edit","teams.delete","teams.manage","team.members.view","team.members.manage"]', 1, strftime('%s', 'now') * 1000, strftime('%s', 'now') * 1000),
19-
('global_user', 'Global User', 'Standard user with basic profile access', '["profile.view","profile.edit","teams.create","teams.view","teams.edit","teams.delete","team.members.view"]', 1, strftime('%s', 'now') * 1000, strftime('%s', 'now') * 1000);
18+
('global_admin', 'Global Administrator', 'Full system access with user management capabilities', '[]', 1, strftime('%s', 'now') * 1000, strftime('%s', 'now') * 1000),
19+
('global_user', 'Global User', 'Standard user with basic profile access', '[]', 1, strftime('%s', 'now') * 1000, strftime('%s', 'now') * 1000),
20+
('team_admin', 'Team Administrator', 'Team management with member and credential access', '[]', 1, strftime('%s', 'now') * 1000, strftime('%s', 'now') * 1000),
21+
('team_user', 'Team User', 'Basic team member with view access', '[]', 1, strftime('%s', 'now') * 1000, strftime('%s', 'now') * 1000);
2022
--> statement-breakpoint
2123

2224
-- Update existing users to have global_user role (all users since role_id starts as NULL)
Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
-- Add MCP permissions to global_admin role
2-
UPDATE roles
3-
SET permissions = '["users.list","users.view","users.edit","users.delete","users.create","roles.manage","system.admin","settings.view","settings.edit","settings.delete","teams.create","teams.view","teams.edit","teams.delete","teams.manage","team.members.view","team.members.manage","mcp.categories.view","mcp.categories.create","mcp.categories.edit","mcp.categories.delete","mcp.servers.global.view","mcp.servers.global.create","mcp.servers.global.edit","mcp.servers.global.delete","mcp.servers.team.view_all","mcp.versions.manage"]',
4-
updated_at = strftime('%s', 'now') * 1000
5-
WHERE id = 'global_admin';
1+
-- MCP permissions are now managed by RoleSyncService on server startup
2+
-- This migration is kept for historical purposes but no longer performs any operations
3+
-- All role permissions are automatically synced from roleService.ts getDefaultPermissions()
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/**
2+
* Centralized Permission Registry
3+
*
4+
* This is the single source of truth for all permissions and role definitions.
5+
* All other files should import from this module to ensure consistency.
6+
*/
7+
8+
// Define all role permissions in one place
9+
export const ROLE_DEFINITIONS = {
10+
global_admin: [
11+
'users.list',
12+
'users.view',
13+
'users.edit',
14+
'users.delete',
15+
'users.create',
16+
'roles.manage',
17+
'system.admin',
18+
'settings.view',
19+
'settings.edit',
20+
'settings.delete',
21+
'teams.create',
22+
'teams.view',
23+
'teams.edit',
24+
'teams.delete',
25+
'teams.manage',
26+
'team.members.view',
27+
'team.members.manage',
28+
'mcp.categories.view',
29+
'mcp.categories.create',
30+
'mcp.categories.edit',
31+
'mcp.categories.delete',
32+
'mcp.servers.read',
33+
'mcp.servers.global.view',
34+
'mcp.servers.global.create',
35+
'mcp.servers.global.edit',
36+
'mcp.servers.global.delete',
37+
'mcp.servers.team.view_all',
38+
'mcp.versions.manage',
39+
],
40+
global_user: [
41+
'profile.view',
42+
'profile.edit',
43+
'teams.create',
44+
'teams.view',
45+
'teams.edit',
46+
'teams.delete',
47+
'team.members.view',
48+
'mcp.servers.read',
49+
],
50+
team_admin: [
51+
'teams.view',
52+
'teams.edit',
53+
'teams.delete',
54+
'teams.manage',
55+
'team.members.view',
56+
'team.members.manage',
57+
'cloud_credentials.view',
58+
'cloud_credentials.create',
59+
'cloud_credentials.edit',
60+
'cloud_credentials.delete'
61+
],
62+
team_user: [
63+
'teams.view',
64+
'team.members.view',
65+
'cloud_credentials.view'
66+
],
67+
} as const;
68+
69+
// Auto-generate available permissions from all role definitions
70+
export const AVAILABLE_PERMISSIONS = [
71+
...new Set(
72+
Object.values(ROLE_DEFINITIONS).flat()
73+
)
74+
].sort();
75+
76+
// Export types for TypeScript
77+
export type RoleId = keyof typeof ROLE_DEFINITIONS;
78+
export type Permission = typeof AVAILABLE_PERMISSIONS[number];
79+
80+
// Helper functions
81+
export function getRolePermissions(roleId: RoleId): string[] {
82+
return [...ROLE_DEFINITIONS[roleId]];
83+
}
84+
85+
export function getAllRoleDefinitions(): Record<string, string[]> {
86+
const result: Record<string, string[]> = {};
87+
for (const [roleId, permissions] of Object.entries(ROLE_DEFINITIONS)) {
88+
result[roleId] = [...permissions];
89+
}
90+
return result;
91+
}
92+
93+
export function isValidPermission(permission: string): permission is Permission {
94+
return (AVAILABLE_PERMISSIONS as readonly string[]).includes(permission);
95+
}
96+
97+
export function isValidRole(roleId: string): roleId is RoleId {
98+
return roleId in ROLE_DEFINITIONS;
99+
}

services/backend/src/routes/roles/schemas.ts

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { z } from 'zod';
2+
import { AVAILABLE_PERMISSIONS } from '../../permissions';
23

34
// Role schemas
45
export const RoleSchema = z.object({
@@ -59,31 +60,7 @@ export type UpdateRoleInput = z.infer<typeof UpdateRoleSchema>;
5960
export type AssignRoleInput = z.infer<typeof AssignRoleSchema>;
6061
export type UpdateUserInput = z.infer<typeof UpdateUserSchema>;
6162

62-
// Available permissions list
63-
export const AVAILABLE_PERMISSIONS = [
64-
'users.list',
65-
'users.view',
66-
'users.edit',
67-
'users.delete',
68-
'users.create',
69-
'roles.manage',
70-
'system.admin',
71-
'settings.view',
72-
'settings.edit',
73-
'settings.delete',
74-
'profile.view',
75-
'profile.edit',
76-
'teams.create',
77-
'teams.view',
78-
'teams.edit',
79-
'teams.delete',
80-
'teams.manage',
81-
'team.members.view',
82-
'team.members.manage',
83-
'cloud_credentials.view',
84-
'cloud_credentials.create',
85-
'cloud_credentials.edit',
86-
'cloud_credentials.delete',
87-
] as const;
88-
63+
// Available permissions are now imported from centralized permissions registry
64+
// This ensures consistency across the entire application
65+
export { AVAILABLE_PERMISSIONS } from '../../permissions';
8966
export type Permission = typeof AVAILABLE_PERMISSIONS[number];

services/backend/src/server.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
import { GlobalSettingsInitService } from './global-settings'
3434
import { GlobalSettings } from './global-settings/helpers';
3535
import { GlobalSettingsService } from './services/globalSettingsService'; // Import the service
36+
import { RoleSyncService } from './services/roleSyncService'; // Import the role sync service
3637
import type SqliteDriver from 'better-sqlite3'; // For type checking in onClose
3738
import type { FastifyInstance } from 'fastify'
3839

@@ -98,6 +99,22 @@ export async function initializeDatabaseDependentServices(
9899
await initializePluginDatabases(dbInstance, dbExtensions, server.log);
99100
server.log.debug('✅ Plugin database extensions initialized');
100101

102+
// Synchronize role permissions from code to database
103+
try {
104+
server.log.debug('🔄 Starting role synchronization...');
105+
const roleSyncService = new RoleSyncService(server.log);
106+
await roleSyncService.syncRoles();
107+
server.log.debug('✅ Role synchronization completed');
108+
} catch (roleSyncError) {
109+
server.log.error('❌ Role synchronization failed:', {
110+
error: roleSyncError,
111+
message: roleSyncError instanceof Error ? roleSyncError.message : 'Unknown error',
112+
stack: roleSyncError instanceof Error ? roleSyncError.stack : 'No stack trace'
113+
});
114+
// Don't throw - continue with startup but log the error
115+
server.log.warn('⚠️ Continuing without role synchronization due to error');
116+
}
117+
101118
// Initialize global settings with comprehensive debugging
102119
try {
103120
server.log.debug('🔄 Starting global settings initialization...');

services/backend/src/services/roleService.ts

Lines changed: 3 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
22
import { getDb, getSchema } from '../db';
33
import { eq } from 'drizzle-orm';
4+
import { getAllRoleDefinitions } from '../permissions';
45

56
export interface Role {
67
id: string;
@@ -238,66 +239,9 @@ export class RoleService {
238239

239240
/**
240241
* Get default permissions for each role
242+
* Uses centralized permission definitions
241243
*/
242244
static getDefaultPermissions() {
243-
return {
244-
global_admin: [
245-
'users.list',
246-
'users.view',
247-
'users.edit',
248-
'users.delete',
249-
'users.create',
250-
'roles.manage',
251-
'system.admin',
252-
'settings.view',
253-
'settings.edit',
254-
'settings.delete',
255-
'teams.create',
256-
'teams.view',
257-
'teams.edit',
258-
'teams.delete',
259-
'teams.manage',
260-
'team.members.view',
261-
'team.members.manage',
262-
'mcp.categories.view',
263-
'mcp.categories.create',
264-
'mcp.categories.edit',
265-
'mcp.categories.delete',
266-
'mcp.servers.read',
267-
'mcp.servers.global.view',
268-
'mcp.servers.global.create',
269-
'mcp.servers.global.edit',
270-
'mcp.servers.global.delete',
271-
'mcp.servers.team.view_all',
272-
'mcp.versions.manage',
273-
],
274-
global_user: [
275-
'profile.view',
276-
'profile.edit',
277-
'teams.create',
278-
'teams.view',
279-
'teams.edit',
280-
'teams.delete',
281-
'team.members.view',
282-
'mcp.servers.read',
283-
],
284-
team_admin: [
285-
'teams.view',
286-
'teams.edit',
287-
'teams.delete',
288-
'teams.manage',
289-
'team.members.view',
290-
'team.members.manage',
291-
'cloud_credentials.view',
292-
'cloud_credentials.create',
293-
'cloud_credentials.edit',
294-
'cloud_credentials.delete'
295-
],
296-
team_user: [
297-
'teams.view',
298-
'team.members.view',
299-
'cloud_credentials.view'
300-
],
301-
};
245+
return getAllRoleDefinitions();
302246
}
303247
}

0 commit comments

Comments
 (0)