mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-16 02:40:39 +09:00
Scope plugin config save/test by company
This commit is contained in:
parent
4272b31136
commit
5317029ef4
6 changed files with 348 additions and 43 deletions
|
|
@ -23,6 +23,11 @@ const mockLifecycle = vi.hoisted(() => ({
|
|||
disable: vi.fn(),
|
||||
}));
|
||||
|
||||
const mockWorkerManager = vi.hoisted(() => ({
|
||||
call: vi.fn(),
|
||||
isRunning: vi.fn(() => false),
|
||||
}));
|
||||
|
||||
vi.mock("../services/plugin-registry.js", () => ({
|
||||
pluginRegistryService: () => mockRegistry,
|
||||
}));
|
||||
|
|
@ -437,6 +442,61 @@ describe.sequential("plugin install and upgrade authz", () => {
|
|||
expect(res.body.configJson).toEqual({ botName: "company-a" });
|
||||
}, 20_000);
|
||||
|
||||
it("rejects plugin config tests with secret refs when company scope is missing", async () => {
|
||||
readyPlugin();
|
||||
|
||||
const { app } = await createApp(boardActor({
|
||||
isInstanceAdmin: true,
|
||||
companyIds: [companyA],
|
||||
}), {}, {
|
||||
bridgeDeps: { workerManager: mockWorkerManager },
|
||||
});
|
||||
|
||||
const res = await request(app)
|
||||
.post(`/api/plugins/${pluginId}/config/test`)
|
||||
.send({
|
||||
configJson: {
|
||||
apiKeyRef: "77777777-7777-4777-8777-777777777777",
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.status).toBe(422);
|
||||
expect(res.body.error).toMatch(/secret references require companyId/i);
|
||||
expect(mockWorkerManager.call).not.toHaveBeenCalled();
|
||||
}, 20_000);
|
||||
|
||||
it("passes company-scoped plugin config tests through when companyId is provided", async () => {
|
||||
readyPlugin();
|
||||
mockWorkerManager.call.mockResolvedValueOnce({ ok: true, warnings: [] });
|
||||
|
||||
const { app } = await createApp(boardActor({
|
||||
isInstanceAdmin: true,
|
||||
companyIds: [companyA],
|
||||
}), {}, {
|
||||
bridgeDeps: { workerManager: mockWorkerManager },
|
||||
});
|
||||
|
||||
const res = await request(app)
|
||||
.post(`/api/plugins/${pluginId}/config/test`)
|
||||
.send({
|
||||
companyId: companyA,
|
||||
configJson: {
|
||||
apiKeyRef: "77777777-7777-4777-8777-777777777777",
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(mockWorkerManager.call).toHaveBeenCalledWith(
|
||||
pluginId,
|
||||
"validateConfig",
|
||||
{
|
||||
config: {
|
||||
apiKeyRef: "77777777-7777-4777-8777-777777777777",
|
||||
},
|
||||
},
|
||||
);
|
||||
}, 20_000);
|
||||
|
||||
it("allows instance admins to upgrade plugins", async () => {
|
||||
const pluginId = "11111111-1111-4111-8111-111111111111";
|
||||
mockRegistry.getById.mockResolvedValue({
|
||||
|
|
|
|||
|
|
@ -2298,11 +2298,12 @@ export function pluginRoutes(
|
|||
return;
|
||||
}
|
||||
|
||||
const body = req.body as { configJson?: Record<string, unknown> } | undefined;
|
||||
const body = req.body as { configJson?: Record<string, unknown>; companyId?: string } | undefined;
|
||||
if (!body?.configJson || typeof body.configJson !== "object") {
|
||||
res.status(400).json({ error: '"configJson" is required and must be an object' });
|
||||
return;
|
||||
}
|
||||
const companyId = resolvePluginConfigCompanyId(req);
|
||||
|
||||
// Fast schema-level rejection before hitting the worker RPC.
|
||||
const schema = plugin.manifestJson?.instanceConfigSchema;
|
||||
|
|
@ -2318,6 +2319,11 @@ export function pluginRoutes(
|
|||
}
|
||||
|
||||
try {
|
||||
const secretRefsByPath = extractSecretRefPathsFromConfig(body.configJson, schema);
|
||||
if (secretRefsByPath.size > 0 && !companyId) {
|
||||
res.status(422).json({ error: "Plugin secret references require companyId" });
|
||||
return;
|
||||
}
|
||||
const result = await bridgeDeps.workerManager.call(
|
||||
plugin.id,
|
||||
"validateConfig",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue