mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-27 12:13:30 +00:00
🧱 refactor: Require Broad Config Management for Base Field Mutations (#14775)
This commit is contained in:
parent
e696b07619
commit
8f1f961212
2 changed files with 84 additions and 2 deletions
|
|
@ -1728,7 +1728,7 @@ describe('createAdminConfigHandlers', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('scope-lifecycle: __base__ short-circuit', () => {
|
||||
describe('invariant: __base__ requires broad manage:configs', () => {
|
||||
it('upsert against __base__ returns 403 for assign-only caller', async () => {
|
||||
const { handlers, deps } = createHandlers({
|
||||
hasConfigCapability: jest.fn().mockResolvedValue(false),
|
||||
|
|
@ -1794,6 +1794,70 @@ describe('createAdminConfigHandlers', () => {
|
|||
expect(res.statusCode).toBe(201);
|
||||
expect(deps.upsertConfig).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('patch against __base__ returns 403 for a section-scoped manager', async () => {
|
||||
const { handlers, deps } = createHandlers({
|
||||
hasConfigCapability: jest.fn().mockResolvedValueOnce(false).mockResolvedValueOnce(true),
|
||||
});
|
||||
const req = mockReq({
|
||||
params: { principalType: 'role', principalId: '__base__' },
|
||||
body: { entries: [{ fieldPath: 'memory.context', value: 'updated' }] },
|
||||
});
|
||||
const res = mockRes();
|
||||
|
||||
await handlers.patchConfigField(req, res);
|
||||
|
||||
expect(res.statusCode).toBe(403);
|
||||
expect(deps.patchConfigFields).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('tombstone against __base__ returns 403 for a section-scoped manager', async () => {
|
||||
const { handlers, deps } = createHandlers({
|
||||
hasConfigCapability: jest.fn().mockResolvedValueOnce(false).mockResolvedValueOnce(true),
|
||||
});
|
||||
const req = mockReq({
|
||||
params: { principalType: 'role', principalId: '__base__' },
|
||||
body: { fieldPath: 'memory.context' },
|
||||
});
|
||||
const res = mockRes();
|
||||
|
||||
await handlers.tombstoneConfigField(req, res);
|
||||
|
||||
expect(res.statusCode).toBe(403);
|
||||
expect(deps.tombstoneConfigField).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('field delete against __base__ returns 403 for a section-scoped manager', async () => {
|
||||
const { handlers, deps } = createHandlers({
|
||||
hasConfigCapability: jest.fn().mockResolvedValueOnce(false).mockResolvedValueOnce(true),
|
||||
});
|
||||
const req = mockReq({
|
||||
params: { principalType: 'role', principalId: '__base__' },
|
||||
query: { fieldPath: 'memory.context' },
|
||||
});
|
||||
const res = mockRes();
|
||||
|
||||
await handlers.deleteConfigField(req, res);
|
||||
|
||||
expect(res.statusCode).toBe(403);
|
||||
expect(deps.unsetConfigField).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('patch against __base__ succeeds for a broad-manage caller', async () => {
|
||||
const { handlers, deps } = createHandlers({
|
||||
hasConfigCapability: jest.fn().mockResolvedValue(true),
|
||||
});
|
||||
const req = mockReq({
|
||||
params: { principalType: 'role', principalId: '__base__' },
|
||||
body: { entries: [{ fieldPath: 'memory.context', value: 'updated' }] },
|
||||
});
|
||||
const res = mockRes();
|
||||
|
||||
await handlers.patchConfigField(req, res);
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(deps.patchConfigFields).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('scope-lifecycle: atomic empty-state guard for assign-only callers', () => {
|
||||
|
|
|
|||
|
|
@ -747,6 +747,10 @@ export function createAdminConfigHandlers(deps: AdminConfigDeps): {
|
|||
|
||||
const hasBroadManage = await hasConfigCapability(user, null, 'manage');
|
||||
|
||||
if (principalId === BASE_CONFIG_PRINCIPAL_ID && !hasBroadManage) {
|
||||
return res.status(403).json({ error: 'Insufficient permissions' });
|
||||
}
|
||||
|
||||
if (validEntries.length === 0) {
|
||||
if (!hasBroadManage) {
|
||||
return res.status(403).json({ error: 'Insufficient permissions' });
|
||||
|
|
@ -858,6 +862,11 @@ export function createAdminConfigHandlers(deps: AdminConfigDeps): {
|
|||
const section = getTopLevelSection(fieldPath);
|
||||
|
||||
const hasBroadManage = await hasConfigCapability(user, null, 'manage');
|
||||
|
||||
if (principalId === BASE_CONFIG_PRINCIPAL_ID && !hasBroadManage) {
|
||||
return res.status(403).json({ error: 'Insufficient permissions' });
|
||||
}
|
||||
|
||||
if (
|
||||
!hasBroadManage &&
|
||||
!(await hasConfigCapability(user, section as ConfigSection, 'manage'))
|
||||
|
|
@ -950,7 +959,16 @@ export function createAdminConfigHandlers(deps: AdminConfigDeps): {
|
|||
|
||||
const section = getTopLevelSection(fieldPath);
|
||||
|
||||
if (!(await hasConfigCapability(user, section as ConfigSection, 'manage'))) {
|
||||
const hasBroadManage = await hasConfigCapability(user, null, 'manage');
|
||||
|
||||
if (principalId === BASE_CONFIG_PRINCIPAL_ID && !hasBroadManage) {
|
||||
return res.status(403).json({ error: 'Insufficient permissions' });
|
||||
}
|
||||
|
||||
if (
|
||||
!hasBroadManage &&
|
||||
!(await hasConfigCapability(user, section as ConfigSection, 'manage'))
|
||||
) {
|
||||
return res.status(403).json({
|
||||
error: `Insufficient permissions for config section: ${section}`,
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue