mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-03-28 10:27:19 +01:00
🎭 fix: Set Explicit Permission Defaults for USER Role in roleDefaults (#12308)
* fix: set explicit permission defaults for USER role in roleDefaults
Previously several permission types for the USER role had empty
objects in roleDefaults, causing the getPermissionValue fallback to
resolve SHARE/CREATE via the zod schema defaults on fresh installs.
This silently granted users MCP server creation ability and left
share permissions ambiguous.
Sets explicit defaults for all multi-field permission types:
- PROMPTS/AGENTS: USE and CREATE true, SHARE false
- MCP_SERVERS: USE true, CREATE/SHARE false
- REMOTE_AGENTS: all false
Adds regression tests covering the exact reported scenarios (fresh
install with `agents: { use: true }`, restart preserving admin-panel
overrides) and structural guards against future permission schema
expansions missing explicit USER defaults.
Closes #12306.
* fix: guard MCP_SERVERS.CREATE against configDefaults fallback + add migration
The roleDefaults fix alone was insufficient: loadDefaultInterface propagates
configDefaults.mcpServers.create=true as tier-1 in getPermissionValue, overriding
the roleDefault of false. This commit:
- Adds conditional guards for MCP_SERVERS.CREATE and REMOTE_AGENTS.CREATE matching
the existing AGENTS/PROMPTS pattern (only include CREATE when explicitly configured
in yaml OR on fresh install)
- Uses raw interfaceConfig for MCP_SERVERS.CREATE tier-1 instead of loadedInterface
(which includes configDefaults fallback)
- Adds one-time migration backfill: corrects existing MCP_SERVERS.CREATE=true for
USER role in DB when no explicit yaml config is present
- Adds restart-scenario and migration regression tests for MCP_SERVERS
- Cleans up roles.spec.ts: for..of loops, Permissions[] typing, Set for lookups,
removes unnecessary aliases, improves JSDoc for exclusion list
- Fixes misleading test name for agents regression test
- Removes redundant not.toHaveProperty assertions after strict toEqual
* fix: use raw interfaceConfig for REMOTE_AGENTS.CREATE tier-1 (consistency)
Aligns REMOTE_AGENTS.CREATE with the MCP_SERVERS.CREATE fix — reads from
raw interfaceConfig instead of loadedInterface to prevent a future
configDefaults fallback from silently overriding the roleDefault.
This commit is contained in:
parent
9cb5ac63f8
commit
b189972381
4 changed files with 510 additions and 19 deletions
|
|
@ -398,7 +398,7 @@ describe('updateInterfacePermissions - permissions', () => {
|
|||
[PermissionTypes.FILE_CITATIONS]: { [Permissions.USE]: true },
|
||||
[PermissionTypes.MCP_SERVERS]: {
|
||||
[Permissions.USE]: true,
|
||||
[Permissions.CREATE]: true,
|
||||
[Permissions.CREATE]: false,
|
||||
[Permissions.SHARE]: false,
|
||||
[Permissions.SHARE_PUBLIC]: false,
|
||||
},
|
||||
|
|
@ -555,7 +555,7 @@ describe('updateInterfacePermissions - permissions', () => {
|
|||
[PermissionTypes.FILE_CITATIONS]: { [Permissions.USE]: true },
|
||||
[PermissionTypes.MCP_SERVERS]: {
|
||||
[Permissions.USE]: true,
|
||||
[Permissions.CREATE]: true,
|
||||
[Permissions.CREATE]: false,
|
||||
[Permissions.SHARE]: false,
|
||||
[Permissions.SHARE_PUBLIC]: false,
|
||||
},
|
||||
|
|
@ -699,7 +699,7 @@ describe('updateInterfacePermissions - permissions', () => {
|
|||
[PermissionTypes.FILE_CITATIONS]: { [Permissions.USE]: true },
|
||||
[PermissionTypes.MCP_SERVERS]: {
|
||||
[Permissions.USE]: true,
|
||||
[Permissions.CREATE]: true,
|
||||
[Permissions.CREATE]: false,
|
||||
[Permissions.SHARE]: false,
|
||||
[Permissions.SHARE_PUBLIC]: false,
|
||||
},
|
||||
|
|
@ -848,7 +848,7 @@ describe('updateInterfacePermissions - permissions', () => {
|
|||
[PermissionTypes.FILE_CITATIONS]: { [Permissions.USE]: true },
|
||||
[PermissionTypes.MCP_SERVERS]: {
|
||||
[Permissions.USE]: true,
|
||||
[Permissions.CREATE]: true,
|
||||
[Permissions.CREATE]: false,
|
||||
[Permissions.SHARE]: false,
|
||||
[Permissions.SHARE_PUBLIC]: false,
|
||||
},
|
||||
|
|
@ -1002,7 +1002,7 @@ describe('updateInterfacePermissions - permissions', () => {
|
|||
[PermissionTypes.FILE_CITATIONS]: { [Permissions.USE]: true },
|
||||
[PermissionTypes.MCP_SERVERS]: {
|
||||
[Permissions.USE]: true,
|
||||
[Permissions.CREATE]: true,
|
||||
[Permissions.CREATE]: false,
|
||||
[Permissions.SHARE]: false,
|
||||
[Permissions.SHARE_PUBLIC]: false,
|
||||
},
|
||||
|
|
@ -2194,4 +2194,302 @@ describe('updateInterfacePermissions - permissions', () => {
|
|||
[Permissions.SHARE_PUBLIC]: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should populate all default agent permissions on fresh install with object use config (regression: #12306)', async () => {
|
||||
const config = {
|
||||
interface: {
|
||||
agents: { use: 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,
|
||||
);
|
||||
const adminCall = mockUpdateAccessPermissions.mock.calls.find(
|
||||
(call) => call[0] === SystemRoles.ADMIN,
|
||||
);
|
||||
|
||||
expect(userCall[1][PermissionTypes.AGENTS]).toEqual({
|
||||
[Permissions.USE]: true,
|
||||
[Permissions.CREATE]: true,
|
||||
[Permissions.SHARE]: false,
|
||||
[Permissions.SHARE_PUBLIC]: false,
|
||||
});
|
||||
|
||||
expect(adminCall[1][PermissionTypes.AGENTS]).toEqual({
|
||||
[Permissions.USE]: true,
|
||||
[Permissions.CREATE]: true,
|
||||
[Permissions.SHARE]: true,
|
||||
[Permissions.SHARE_PUBLIC]: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should preserve admin-panel changes to USER agents.CREATE across restart (regression: #12306 restart)', async () => {
|
||||
mockGetRoleByName.mockResolvedValue({
|
||||
permissions: {
|
||||
[PermissionTypes.AGENTS]: {
|
||||
[Permissions.USE]: true,
|
||||
[Permissions.CREATE]: false,
|
||||
[Permissions.SHARE]: false,
|
||||
[Permissions.SHARE_PUBLIC]: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const config = {
|
||||
interface: {
|
||||
agents: { use: 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.AGENTS]).toEqual({
|
||||
[Permissions.USE]: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should preserve all admin-panel changes when agents is not in yaml config (regression: #12306 restart)', async () => {
|
||||
mockGetRoleByName.mockResolvedValue({
|
||||
permissions: {
|
||||
[PermissionTypes.AGENTS]: {
|
||||
[Permissions.USE]: false,
|
||||
[Permissions.CREATE]: false,
|
||||
[Permissions.SHARE]: false,
|
||||
[Permissions.SHARE_PUBLIC]: false,
|
||||
},
|
||||
[PermissionTypes.PROMPTS]: {
|
||||
[Permissions.USE]: false,
|
||||
[Permissions.CREATE]: false,
|
||||
[Permissions.SHARE]: false,
|
||||
[Permissions.SHARE_PUBLIC]: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const config = {};
|
||||
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]).not.toHaveProperty(PermissionTypes.AGENTS);
|
||||
expect(userCall[1]).not.toHaveProperty(PermissionTypes.PROMPTS);
|
||||
});
|
||||
|
||||
it('should not grant USER share for prompts when only use is configured (regression: #12306)', async () => {
|
||||
const config = {
|
||||
interface: {
|
||||
prompts: { use: 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,
|
||||
);
|
||||
const adminCall = mockUpdateAccessPermissions.mock.calls.find(
|
||||
(call) => call[0] === SystemRoles.ADMIN,
|
||||
);
|
||||
|
||||
expect(userCall[1][PermissionTypes.PROMPTS]).toEqual({
|
||||
[Permissions.USE]: true,
|
||||
[Permissions.CREATE]: true,
|
||||
[Permissions.SHARE]: false,
|
||||
[Permissions.SHARE_PUBLIC]: false,
|
||||
});
|
||||
|
||||
expect(adminCall[1][PermissionTypes.PROMPTS]).toEqual({
|
||||
[Permissions.USE]: true,
|
||||
[Permissions.CREATE]: true,
|
||||
[Permissions.SHARE]: true,
|
||||
[Permissions.SHARE_PUBLIC]: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should not grant USER create for mcpServers when only use is configured (regression: #12306)', async () => {
|
||||
const config = {
|
||||
interface: {
|
||||
mcpServers: { use: 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,
|
||||
);
|
||||
const adminCall = mockUpdateAccessPermissions.mock.calls.find(
|
||||
(call) => call[0] === SystemRoles.ADMIN,
|
||||
);
|
||||
|
||||
expect(userCall[1][PermissionTypes.MCP_SERVERS]).toEqual({
|
||||
[Permissions.USE]: true,
|
||||
[Permissions.CREATE]: false,
|
||||
[Permissions.SHARE]: false,
|
||||
[Permissions.SHARE_PUBLIC]: false,
|
||||
});
|
||||
|
||||
expect(adminCall[1][PermissionTypes.MCP_SERVERS]).toEqual({
|
||||
[Permissions.USE]: true,
|
||||
[Permissions.CREATE]: true,
|
||||
[Permissions.SHARE]: true,
|
||||
[Permissions.SHARE_PUBLIC]: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should preserve existing MCP_SERVERS permissions on restart when mcpServers not in yaml config (regression: #12306 restart)', async () => {
|
||||
mockGetRoleByName.mockResolvedValue({
|
||||
permissions: {
|
||||
[PermissionTypes.MCP_SERVERS]: {
|
||||
[Permissions.USE]: true,
|
||||
[Permissions.CREATE]: true,
|
||||
[Permissions.SHARE]: false,
|
||||
[Permissions.SHARE_PUBLIC]: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const config = {};
|
||||
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.MCP_SERVERS]).toEqual({
|
||||
[Permissions.CREATE]: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should migrate existing MCP_SERVERS.CREATE=true to false for USER when no explicit config (regression: #12306 migration)', async () => {
|
||||
mockGetRoleByName.mockResolvedValue({
|
||||
permissions: {
|
||||
[PermissionTypes.MCP_SERVERS]: {
|
||||
[Permissions.USE]: true,
|
||||
[Permissions.CREATE]: true,
|
||||
[Permissions.SHARE]: false,
|
||||
[Permissions.SHARE_PUBLIC]: false,
|
||||
},
|
||||
[PermissionTypes.AGENTS]: {
|
||||
[Permissions.USE]: true,
|
||||
[Permissions.CREATE]: true,
|
||||
[Permissions.SHARE]: false,
|
||||
[Permissions.SHARE_PUBLIC]: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const config = {};
|
||||
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,
|
||||
);
|
||||
const adminCall = mockUpdateAccessPermissions.mock.calls.find(
|
||||
(call) => call[0] === SystemRoles.ADMIN,
|
||||
);
|
||||
|
||||
expect(userCall[1][PermissionTypes.MCP_SERVERS]).toEqual({
|
||||
[Permissions.CREATE]: false,
|
||||
});
|
||||
expect(userCall[1]).not.toHaveProperty(PermissionTypes.AGENTS);
|
||||
|
||||
expect(adminCall[1]).not.toHaveProperty(PermissionTypes.MCP_SERVERS);
|
||||
expect(adminCall[1]).not.toHaveProperty(PermissionTypes.AGENTS);
|
||||
});
|
||||
|
||||
it('should NOT migrate MCP_SERVERS.CREATE when yaml explicitly sets create: true (regression: #12306 migration)', async () => {
|
||||
mockGetRoleByName.mockResolvedValue({
|
||||
permissions: {
|
||||
[PermissionTypes.MCP_SERVERS]: {
|
||||
[Permissions.USE]: true,
|
||||
[Permissions.CREATE]: true,
|
||||
[Permissions.SHARE]: false,
|
||||
[Permissions.SHARE_PUBLIC]: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const config = {
|
||||
interface: {
|
||||
mcpServers: { use: true, create: 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.MCP_SERVERS][Permissions.CREATE]).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -352,11 +352,19 @@ export async function updateInterfacePermissions({
|
|||
defaultPerms[PermissionTypes.MCP_SERVERS]?.[Permissions.USE],
|
||||
defaults.mcpServers?.use,
|
||||
),
|
||||
[Permissions.CREATE]: getPermissionValue(
|
||||
loadedInterface.mcpServers?.create,
|
||||
defaultPerms[PermissionTypes.MCP_SERVERS]?.[Permissions.CREATE],
|
||||
defaults.mcpServers?.create,
|
||||
),
|
||||
...((typeof interfaceConfig?.mcpServers === 'object' &&
|
||||
'create' in interfaceConfig.mcpServers) ||
|
||||
!existingPermissions?.[PermissionTypes.MCP_SERVERS]
|
||||
? {
|
||||
[Permissions.CREATE]: getPermissionValue(
|
||||
typeof interfaceConfig?.mcpServers === 'object'
|
||||
? interfaceConfig.mcpServers.create
|
||||
: undefined,
|
||||
defaultPerms[PermissionTypes.MCP_SERVERS]?.[Permissions.CREATE],
|
||||
defaults.mcpServers?.create,
|
||||
),
|
||||
}
|
||||
: {}),
|
||||
...((typeof interfaceConfig?.mcpServers === 'object' &&
|
||||
('share' in interfaceConfig.mcpServers || 'public' in interfaceConfig.mcpServers)) ||
|
||||
!existingPermissions?.[PermissionTypes.MCP_SERVERS]
|
||||
|
|
@ -380,11 +388,19 @@ export async function updateInterfacePermissions({
|
|||
defaultPerms[PermissionTypes.REMOTE_AGENTS]?.[Permissions.USE],
|
||||
defaults.remoteAgents?.use,
|
||||
),
|
||||
[Permissions.CREATE]: getPermissionValue(
|
||||
loadedInterface.remoteAgents?.create,
|
||||
defaultPerms[PermissionTypes.REMOTE_AGENTS]?.[Permissions.CREATE],
|
||||
defaults.remoteAgents?.create,
|
||||
),
|
||||
...((typeof interfaceConfig?.remoteAgents === 'object' &&
|
||||
'create' in interfaceConfig.remoteAgents) ||
|
||||
!existingPermissions?.[PermissionTypes.REMOTE_AGENTS]
|
||||
? {
|
||||
[Permissions.CREATE]: getPermissionValue(
|
||||
typeof interfaceConfig?.remoteAgents === 'object'
|
||||
? interfaceConfig.remoteAgents.create
|
||||
: undefined,
|
||||
defaultPerms[PermissionTypes.REMOTE_AGENTS]?.[Permissions.CREATE],
|
||||
defaults.remoteAgents?.create,
|
||||
),
|
||||
}
|
||||
: {}),
|
||||
...((typeof interfaceConfig?.remoteAgents === 'object' &&
|
||||
('share' in interfaceConfig.remoteAgents || 'public' in interfaceConfig.remoteAgents)) ||
|
||||
!existingPermissions?.[PermissionTypes.REMOTE_AGENTS]
|
||||
|
|
@ -511,6 +527,31 @@ export async function updateInterfacePermissions({
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* One-time migration: correct MCP_SERVERS.CREATE for USER role.
|
||||
* Before the explicit roleDefaults fix, Zod schema defaults resolved CREATE to true
|
||||
* for all roles. ADMIN should keep CREATE: true, but USER should have CREATE: false
|
||||
* unless explicitly configured otherwise in librechat.yaml.
|
||||
*/
|
||||
if (roleName === SystemRoles.USER) {
|
||||
const existingMcpPerms = existingPermissions?.[PermissionTypes.MCP_SERVERS];
|
||||
const mcpCreateExplicit =
|
||||
typeof interfaceConfig?.mcpServers === 'object' && 'create' in interfaceConfig.mcpServers;
|
||||
if (
|
||||
existingMcpPerms?.[Permissions.CREATE] === true &&
|
||||
!mcpCreateExplicit &&
|
||||
defaultPerms[PermissionTypes.MCP_SERVERS]?.[Permissions.CREATE] === false
|
||||
) {
|
||||
logger.debug(
|
||||
`Role '${roleName}': Migrating MCP_SERVERS.CREATE from true to false (Zod default correction)`,
|
||||
);
|
||||
permissionsToUpdate[PermissionTypes.MCP_SERVERS] = {
|
||||
...permissionsToUpdate[PermissionTypes.MCP_SERVERS],
|
||||
[Permissions.CREATE]: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Update permissions if any need updating
|
||||
if (Object.keys(permissionsToUpdate).length > 0) {
|
||||
await updateAccessPermissions(roleName, permissionsToUpdate, existingRole);
|
||||
|
|
|
|||
132
packages/data-provider/src/roles.spec.ts
Normal file
132
packages/data-provider/src/roles.spec.ts
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
import { Permissions, PermissionTypes, permissionsSchema } from './permissions';
|
||||
import { SystemRoles, roleDefaults } from './roles';
|
||||
|
||||
const RESOURCE_MANAGEMENT_FIELDS: Permissions[] = [
|
||||
Permissions.CREATE,
|
||||
Permissions.SHARE,
|
||||
Permissions.SHARE_PUBLIC,
|
||||
];
|
||||
|
||||
/**
|
||||
* Permission types where CREATE/SHARE/SHARE_PUBLIC must default to false for USER.
|
||||
* MEMORIES is excluded: its CREATE/READ/UPDATE apply to the user's own private data.
|
||||
* AGENTS/PROMPTS are excluded: CREATE=true is intentional (users own their agents/prompts).
|
||||
* Add new types here if they gate shared/multi-user resources.
|
||||
*/
|
||||
const RESOURCE_PERMISSION_TYPES: PermissionTypes[] = [
|
||||
PermissionTypes.MCP_SERVERS,
|
||||
PermissionTypes.REMOTE_AGENTS,
|
||||
];
|
||||
|
||||
describe('roleDefaults', () => {
|
||||
describe('USER role', () => {
|
||||
const userPerms = roleDefaults[SystemRoles.USER].permissions;
|
||||
|
||||
it('should have explicit values for every field in every multi-field permission type', () => {
|
||||
const schemaShape = permissionsSchema.shape;
|
||||
|
||||
for (const [permType, subSchema] of Object.entries(schemaShape)) {
|
||||
const fieldNames = Object.keys(subSchema.shape);
|
||||
if (fieldNames.length <= 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const userValues =
|
||||
userPerms[permType as PermissionTypes] as Record<string, boolean>;
|
||||
|
||||
for (const field of fieldNames) {
|
||||
expect({
|
||||
permType,
|
||||
field,
|
||||
value: userValues[field],
|
||||
}).toEqual(
|
||||
expect.objectContaining({
|
||||
permType,
|
||||
field,
|
||||
value: expect.any(Boolean),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should never grant CREATE, SHARE, or SHARE_PUBLIC by default for resource-management types', () => {
|
||||
for (const permType of RESOURCE_PERMISSION_TYPES) {
|
||||
const permissions = userPerms[permType] as Record<string, boolean>;
|
||||
for (const field of RESOURCE_MANAGEMENT_FIELDS) {
|
||||
if (permissions[field] === undefined) {
|
||||
continue;
|
||||
}
|
||||
expect({
|
||||
permType,
|
||||
field,
|
||||
value: permissions[field],
|
||||
}).toEqual(
|
||||
expect.objectContaining({
|
||||
permType,
|
||||
field,
|
||||
value: false,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should cover every permission type that has CREATE, SHARE, or SHARE_PUBLIC fields', () => {
|
||||
const schemaShape = permissionsSchema.shape;
|
||||
const restrictedSet = new Set<string>(RESOURCE_PERMISSION_TYPES);
|
||||
|
||||
for (const [permType, subSchema] of Object.entries(schemaShape)) {
|
||||
const fieldNames = Object.keys(subSchema.shape);
|
||||
const hasResourceFields = fieldNames.some((f) => RESOURCE_MANAGEMENT_FIELDS.includes(f as Permissions));
|
||||
if (!hasResourceFields) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const isTracked =
|
||||
restrictedSet.has(permType) ||
|
||||
permType === PermissionTypes.MEMORIES ||
|
||||
permType === PermissionTypes.PROMPTS ||
|
||||
permType === PermissionTypes.AGENTS;
|
||||
|
||||
expect({
|
||||
permType,
|
||||
tracked: isTracked,
|
||||
}).toEqual(
|
||||
expect.objectContaining({
|
||||
permType,
|
||||
tracked: true,
|
||||
}),
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('ADMIN role', () => {
|
||||
const adminPerms = roleDefaults[SystemRoles.ADMIN].permissions;
|
||||
|
||||
it('should have explicit values for every field in every permission type', () => {
|
||||
const schemaShape = permissionsSchema.shape;
|
||||
|
||||
for (const [permType, subSchema] of Object.entries(schemaShape)) {
|
||||
const fieldNames = Object.keys(subSchema.shape);
|
||||
const adminValues =
|
||||
adminPerms[permType as PermissionTypes] as Record<string, boolean>;
|
||||
|
||||
for (const field of fieldNames) {
|
||||
expect({
|
||||
permType,
|
||||
field,
|
||||
value: adminValues[field],
|
||||
}).toEqual(
|
||||
expect.objectContaining({
|
||||
permType,
|
||||
field,
|
||||
value: expect.any(Boolean),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -180,10 +180,20 @@ export const roleDefaults = defaultRolesSchema.parse({
|
|||
[SystemRoles.USER]: {
|
||||
name: SystemRoles.USER,
|
||||
permissions: {
|
||||
[PermissionTypes.PROMPTS]: {},
|
||||
[PermissionTypes.PROMPTS]: {
|
||||
[Permissions.USE]: true,
|
||||
[Permissions.CREATE]: true,
|
||||
[Permissions.SHARE]: false,
|
||||
[Permissions.SHARE_PUBLIC]: false,
|
||||
},
|
||||
[PermissionTypes.BOOKMARKS]: {},
|
||||
[PermissionTypes.MEMORIES]: {},
|
||||
[PermissionTypes.AGENTS]: {},
|
||||
[PermissionTypes.AGENTS]: {
|
||||
[Permissions.USE]: true,
|
||||
[Permissions.CREATE]: true,
|
||||
[Permissions.SHARE]: false,
|
||||
[Permissions.SHARE_PUBLIC]: false,
|
||||
},
|
||||
[PermissionTypes.MULTI_CONVO]: {},
|
||||
[PermissionTypes.TEMPORARY_CHAT]: {},
|
||||
[PermissionTypes.RUN_CODE]: {},
|
||||
|
|
@ -198,8 +208,18 @@ export const roleDefaults = defaultRolesSchema.parse({
|
|||
},
|
||||
[PermissionTypes.FILE_SEARCH]: {},
|
||||
[PermissionTypes.FILE_CITATIONS]: {},
|
||||
[PermissionTypes.MCP_SERVERS]: {},
|
||||
[PermissionTypes.REMOTE_AGENTS]: {},
|
||||
[PermissionTypes.MCP_SERVERS]: {
|
||||
[Permissions.USE]: true,
|
||||
[Permissions.CREATE]: false,
|
||||
[Permissions.SHARE]: false,
|
||||
[Permissions.SHARE_PUBLIC]: false,
|
||||
},
|
||||
[PermissionTypes.REMOTE_AGENTS]: {
|
||||
[Permissions.USE]: false,
|
||||
[Permissions.CREATE]: false,
|
||||
[Permissions.SHARE]: false,
|
||||
[Permissions.SHARE_PUBLIC]: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue