From 4ee3416326a880ad6daabcbdccda83bf81add149 Mon Sep 17 00:00:00 2001 From: Atef Bellaaj Date: Thu, 14 May 2026 15:55:32 +0200 Subject: [PATCH] fix(permissions): honor boolean sharedLinks config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SHARED_LINKS has no USE permission, so boolean config produced an empty update payload — gate conditions only matched object form, making `sharedLinks: false` a no-op on existing perms. --- packages/api/src/app/permissions.spec.ts | 74 ++++++++++++++++++++++++ packages/api/src/app/permissions.ts | 18 ++++-- 2 files changed, 87 insertions(+), 5 deletions(-) diff --git a/packages/api/src/app/permissions.spec.ts b/packages/api/src/app/permissions.spec.ts index 316a9f0db3..059c776aa1 100644 --- a/packages/api/src/app/permissions.spec.ts +++ b/packages/api/src/app/permissions.spec.ts @@ -2641,4 +2641,78 @@ describe('updateInterfacePermissions - permissions', () => { expect(userCall[1][PermissionTypes.MCP_SERVERS][Permissions.CREATE]).toBe(true); }); + + it('should disable all SHARED_LINKS permissions when sharedLinks: false (boolean)', async () => { + mockGetRoleByName.mockResolvedValue({ + permissions: { + [PermissionTypes.SHARED_LINKS]: { + [Permissions.CREATE]: true, + [Permissions.SHARE]: true, + [Permissions.SHARE_PUBLIC]: true, + }, + }, + }); + + const config = { + interface: { + sharedLinks: false, + }, + }; + const configDefaults = { interface: {} } as TConfigDefaults; + const interfaceConfig = await loadDefaultInterface({ config, configDefaults }); + const appConfig = { config, interfaceConfig } as unknown as AppConfig; + + await updateInterfacePermissions({ + appConfig, + getRoleByName: mockGetRoleByName, + updateAccessPermissions: mockUpdateAccessPermissions, + }); + + const userCall = mockUpdateAccessPermissions.mock.calls.find( + (call) => call[0] === SystemRoles.USER, + ); + + expect(userCall[1][PermissionTypes.SHARED_LINKS]).toEqual({ + [Permissions.CREATE]: false, + [Permissions.SHARE]: false, + [Permissions.SHARE_PUBLIC]: false, + }); + }); + + it('should enable all SHARED_LINKS permissions when sharedLinks: true (boolean)', async () => { + mockGetRoleByName.mockResolvedValue({ + permissions: { + [PermissionTypes.SHARED_LINKS]: { + [Permissions.CREATE]: false, + [Permissions.SHARE]: false, + [Permissions.SHARE_PUBLIC]: false, + }, + }, + }); + + const config = { + interface: { + sharedLinks: true, + }, + }; + const configDefaults = { interface: {} } as TConfigDefaults; + const interfaceConfig = await loadDefaultInterface({ config, configDefaults }); + const appConfig = { config, interfaceConfig } as unknown as AppConfig; + + await updateInterfacePermissions({ + appConfig, + getRoleByName: mockGetRoleByName, + updateAccessPermissions: mockUpdateAccessPermissions, + }); + + const userCall = mockUpdateAccessPermissions.mock.calls.find( + (call) => call[0] === SystemRoles.USER, + ); + + expect(userCall[1][PermissionTypes.SHARED_LINKS]).toEqual({ + [Permissions.CREATE]: true, + [Permissions.SHARE]: true, + [Permissions.SHARE_PUBLIC]: true, + }); + }); }); diff --git a/packages/api/src/app/permissions.ts b/packages/api/src/app/permissions.ts index 638dcc068d..b26e041e42 100644 --- a/packages/api/src/app/permissions.ts +++ b/packages/api/src/app/permissions.ts @@ -481,28 +481,36 @@ export async function updateInterfacePermissions({ : {}), }, [PermissionTypes.SHARED_LINKS]: { - ...((typeof interfaceConfig?.sharedLinks === 'object' && + ...(typeof interfaceConfig?.sharedLinks === 'boolean' || + (typeof interfaceConfig?.sharedLinks === 'object' && 'create' in interfaceConfig.sharedLinks) || !existingPermissions?.[PermissionTypes.SHARED_LINKS] ? { [Permissions.CREATE]: getPermissionValue( - getConfigCreate(loadedInterface.sharedLinks), + typeof loadedInterface.sharedLinks === 'boolean' + ? loadedInterface.sharedLinks + : getConfigCreate(loadedInterface.sharedLinks), defaultPerms[PermissionTypes.SHARED_LINKS]?.[Permissions.CREATE], sharedLinksDefaultCreate ?? true, ), } : {}), - ...((typeof interfaceConfig?.sharedLinks === 'object' && + ...(typeof interfaceConfig?.sharedLinks === 'boolean' || + (typeof interfaceConfig?.sharedLinks === 'object' && ('share' in interfaceConfig.sharedLinks || 'public' in interfaceConfig.sharedLinks)) || !existingPermissions?.[PermissionTypes.SHARED_LINKS] ? { [Permissions.SHARE]: getPermissionValue( - getConfigShare(loadedInterface.sharedLinks), + typeof loadedInterface.sharedLinks === 'boolean' + ? loadedInterface.sharedLinks + : getConfigShare(loadedInterface.sharedLinks), defaultPerms[PermissionTypes.SHARED_LINKS]?.[Permissions.SHARE], sharedLinksDefaultShare, ), [Permissions.SHARE_PUBLIC]: getPermissionValue( - getConfigPublic(loadedInterface.sharedLinks), + typeof loadedInterface.sharedLinks === 'boolean' + ? loadedInterface.sharedLinks + : getConfigPublic(loadedInterface.sharedLinks), defaultPerms[PermissionTypes.SHARED_LINKS]?.[Permissions.SHARE_PUBLIC], sharedLinksDefaultPublic, ),