diff --git a/api/models/Role.js b/api/models/Role.js index 1e77cae533..62ec8dfe29 100644 --- a/api/models/Role.js +++ b/api/models/Role.js @@ -132,6 +132,7 @@ async function updateAccessPermissions(roleName, permissionsUpdate) { /** * Initialize default roles in the system. * Creates the default roles (ADMIN, USER) if they don't exist in the database. + * Updates existing roles with new permission types if they're missing. * * @returns {Promise} */ @@ -139,14 +140,27 @@ const initializeRoles = async function () { const defaultRoles = [SystemRoles.ADMIN, SystemRoles.USER]; for (const roleName of defaultRoles) { - let role = await Role.findOne({ name: roleName }).select('name').lean(); + let role = await Role.findOne({ name: roleName }); + if (!role) { + // Create new role if it doesn't exist role = new Role(roleDefaults[roleName]); - await role.save(); + } else { + // Add missing permission types + let isUpdated = false; + for (const permType of Object.values(PermissionTypes)) { + if (!role[permType]) { + role[permType] = roleDefaults[roleName][permType]; + isUpdated = true; + } + } + if (isUpdated) { + await role.save(); + } } + await role.save(); } }; - module.exports = { getRoleByName, initializeRoles, diff --git a/api/models/Role.spec.js b/api/models/Role.spec.js index c183b9d1c3..753df77e67 100644 --- a/api/models/Role.spec.js +++ b/api/models/Role.spec.js @@ -1,9 +1,14 @@ const mongoose = require('mongoose'); const { MongoMemoryServer } = require('mongodb-memory-server'); -const { SystemRoles, PermissionTypes } = require('librechat-data-provider'); -const Role = require('~/models/schema/roleSchema'); -const { updateAccessPermissions } = require('~/models/Role'); +const { + SystemRoles, + PermissionTypes, + roleDefaults, + Permissions, +} = require('librechat-data-provider'); +const { updateAccessPermissions, initializeRoles } = require('~/models/Role'); const getLogStores = require('~/cache/getLogStores'); +const Role = require('~/models/schema/roleSchema'); // Mock the cache jest.mock('~/cache/getLogStores', () => { @@ -195,3 +200,117 @@ describe('updateAccessPermissions', () => { }); }); }); + +describe('initializeRoles', () => { + beforeEach(async () => { + await Role.deleteMany({}); + }); + + it('should create default roles if they do not exist', async () => { + await initializeRoles(); + + const adminRole = await Role.findOne({ name: SystemRoles.ADMIN }).lean(); + const userRole = await Role.findOne({ name: SystemRoles.USER }).lean(); + + expect(adminRole).toBeTruthy(); + expect(userRole).toBeTruthy(); + + // Check if all permission types exist + Object.values(PermissionTypes).forEach((permType) => { + expect(adminRole[permType]).toBeDefined(); + expect(userRole[permType]).toBeDefined(); + }); + + // Check if permissions match defaults (example for ADMIN role) + expect(adminRole[PermissionTypes.PROMPTS].SHARED_GLOBAL).toBe(true); + expect(adminRole[PermissionTypes.BOOKMARKS].USE).toBe(true); + expect(adminRole[PermissionTypes.AGENTS].CREATE).toBe(true); + }); + + it('should not modify existing permissions for existing roles', async () => { + const customUserRole = { + name: SystemRoles.USER, + [PermissionTypes.PROMPTS]: { + [Permissions.USE]: false, + [Permissions.CREATE]: true, + [Permissions.SHARED_GLOBAL]: true, + }, + [PermissionTypes.BOOKMARKS]: { + [Permissions.USE]: false, + }, + }; + + await new Role(customUserRole).save(); + + await initializeRoles(); + + const userRole = await Role.findOne({ name: SystemRoles.USER }).lean(); + + expect(userRole[PermissionTypes.PROMPTS]).toEqual(customUserRole[PermissionTypes.PROMPTS]); + expect(userRole[PermissionTypes.BOOKMARKS]).toEqual(customUserRole[PermissionTypes.BOOKMARKS]); + expect(userRole[PermissionTypes.AGENTS]).toBeDefined(); + }); + + it('should add new permission types to existing roles', async () => { + const partialUserRole = { + name: SystemRoles.USER, + [PermissionTypes.PROMPTS]: roleDefaults[SystemRoles.USER][PermissionTypes.PROMPTS], + [PermissionTypes.BOOKMARKS]: roleDefaults[SystemRoles.USER][PermissionTypes.BOOKMARKS], + }; + + await new Role(partialUserRole).save(); + + await initializeRoles(); + + const userRole = await Role.findOne({ name: SystemRoles.USER }).lean(); + + expect(userRole[PermissionTypes.AGENTS]).toBeDefined(); + expect(userRole[PermissionTypes.AGENTS].CREATE).toBeDefined(); + expect(userRole[PermissionTypes.AGENTS].USE).toBeDefined(); + expect(userRole[PermissionTypes.AGENTS].SHARED_GLOBAL).toBeDefined(); + }); + + it('should handle multiple runs without duplicating or modifying data', async () => { + await initializeRoles(); + await initializeRoles(); + + const adminRoles = await Role.find({ name: SystemRoles.ADMIN }); + const userRoles = await Role.find({ name: SystemRoles.USER }); + + expect(adminRoles).toHaveLength(1); + expect(userRoles).toHaveLength(1); + + const adminRole = adminRoles[0].toObject(); + const userRole = userRoles[0].toObject(); + + // Check if all permission types exist + Object.values(PermissionTypes).forEach((permType) => { + expect(adminRole[permType]).toBeDefined(); + expect(userRole[permType]).toBeDefined(); + }); + }); + + it('should update roles with missing permission types from roleDefaults', async () => { + const partialAdminRole = { + name: SystemRoles.ADMIN, + [PermissionTypes.PROMPTS]: { + [Permissions.USE]: false, + [Permissions.CREATE]: false, + [Permissions.SHARED_GLOBAL]: false, + }, + [PermissionTypes.BOOKMARKS]: roleDefaults[SystemRoles.ADMIN][PermissionTypes.BOOKMARKS], + }; + + await new Role(partialAdminRole).save(); + + await initializeRoles(); + + const adminRole = await Role.findOne({ name: SystemRoles.ADMIN }).lean(); + + expect(adminRole[PermissionTypes.PROMPTS]).toEqual(partialAdminRole[PermissionTypes.PROMPTS]); + expect(adminRole[PermissionTypes.AGENTS]).toBeDefined(); + expect(adminRole[PermissionTypes.AGENTS].CREATE).toBeDefined(); + expect(adminRole[PermissionTypes.AGENTS].USE).toBeDefined(); + expect(adminRole[PermissionTypes.AGENTS].SHARED_GLOBAL).toBeDefined(); + }); +}); diff --git a/packages/data-provider/src/roles.ts b/packages/data-provider/src/roles.ts index 52cda1be3f..0da92c212c 100644 --- a/packages/data-provider/src/roles.ts +++ b/packages/data-provider/src/roles.ts @@ -46,7 +46,7 @@ export const promptPermissionsSchema = z.object({ [Permissions.SHARED_GLOBAL]: z.boolean().default(false), [Permissions.USE]: z.boolean().default(true), [Permissions.CREATE]: z.boolean().default(true), - [Permissions.SHARE]: z.boolean().default(false), + // [Permissions.SHARE]: z.boolean().default(false), }); export const bookmarkPermissionsSchema = z.object({ @@ -57,7 +57,7 @@ export const agentPermissionsSchema = z.object({ [Permissions.SHARED_GLOBAL]: z.boolean().default(false), [Permissions.USE]: z.boolean().default(true), [Permissions.CREATE]: z.boolean().default(true), - [Permissions.SHARE]: z.boolean().default(false), + // [Permissions.SHARE]: z.boolean().default(false), }); export const roleSchema = z.object({ @@ -79,7 +79,7 @@ const defaultRolesSchema = z.object({ [Permissions.SHARED_GLOBAL]: z.boolean().default(true), [Permissions.USE]: z.boolean().default(true), [Permissions.CREATE]: z.boolean().default(true), - [Permissions.SHARE]: z.boolean().default(true), + // [Permissions.SHARE]: z.boolean().default(true), }), [PermissionTypes.BOOKMARKS]: bookmarkPermissionsSchema.extend({ [Permissions.USE]: z.boolean().default(true), @@ -88,7 +88,7 @@ const defaultRolesSchema = z.object({ [Permissions.SHARED_GLOBAL]: z.boolean().default(true), [Permissions.USE]: z.boolean().default(true), [Permissions.CREATE]: z.boolean().default(true), - [Permissions.SHARE]: z.boolean().default(true), + // [Permissions.SHARE]: z.boolean().default(true), }), }), [SystemRoles.USER]: roleSchema.extend({