mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-14 01:50:39 +09:00
fix(plugin): stop secret auth fallback to global config
This commit is contained in:
parent
8969a713a1
commit
1b3e1e3745
3 changed files with 35 additions and 8 deletions
|
|
@ -1,13 +1,13 @@
|
|||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
getConfig: vi.fn(),
|
||||
getConfigExactScope: vi.fn(),
|
||||
resolveSecretValue: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../services/plugin-registry.js", () => ({
|
||||
pluginRegistryService: () => ({
|
||||
getConfig: mocks.getConfig,
|
||||
getConfigExactScope: mocks.getConfigExactScope,
|
||||
}),
|
||||
}));
|
||||
|
||||
|
|
@ -40,7 +40,7 @@ const manifest = {
|
|||
|
||||
describe("createPluginSecretsHandler runtime company scoping", () => {
|
||||
beforeEach(() => {
|
||||
mocks.getConfig.mockReset();
|
||||
mocks.getConfigExactScope.mockReset();
|
||||
mocks.resolveSecretValue.mockReset();
|
||||
});
|
||||
|
||||
|
|
@ -54,12 +54,12 @@ describe("createPluginSecretsHandler runtime company scoping", () => {
|
|||
await expect(handler.resolve({ secretRef })).rejects.toThrow(
|
||||
PLUGIN_SECRET_REFS_REQUIRE_COMPANY_MESSAGE,
|
||||
);
|
||||
expect(mocks.getConfig).not.toHaveBeenCalled();
|
||||
expect(mocks.getConfigExactScope).not.toHaveBeenCalled();
|
||||
expect(mocks.resolveSecretValue).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("rejects a secret ref that is not referenced by that company's plugin config", async () => {
|
||||
mocks.getConfig.mockResolvedValue({
|
||||
mocks.getConfigExactScope.mockResolvedValue({
|
||||
configJson: {
|
||||
apiKeyRef: "88888888-8888-4888-8888-888888888888",
|
||||
},
|
||||
|
|
@ -74,12 +74,12 @@ describe("createPluginSecretsHandler runtime company scoping", () => {
|
|||
await expect(handler.resolve({ secretRef, companyId })).rejects.toThrow(
|
||||
/not referenced by this company's plugin config/i,
|
||||
);
|
||||
expect(mocks.getConfig).toHaveBeenCalledWith(pluginId, companyId);
|
||||
expect(mocks.getConfigExactScope).toHaveBeenCalledWith(pluginId, companyId);
|
||||
expect(mocks.resolveSecretValue).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("resolves only through the company plugin binding context from saved config", async () => {
|
||||
mocks.getConfig.mockResolvedValue({
|
||||
mocks.getConfigExactScope.mockResolvedValue({
|
||||
configJson: {
|
||||
apiKeyRef: secretRef,
|
||||
},
|
||||
|
|
@ -102,4 +102,20 @@ describe("createPluginSecretsHandler runtime company scoping", () => {
|
|||
pluginId,
|
||||
});
|
||||
});
|
||||
|
||||
it("does not authorize a company-scoped secret resolution from legacy global config fallback", async () => {
|
||||
mocks.getConfigExactScope.mockResolvedValue(null);
|
||||
|
||||
const handler = createPluginSecretsHandler({
|
||||
db: {} as never,
|
||||
pluginId,
|
||||
manifest: manifest as never,
|
||||
});
|
||||
|
||||
await expect(handler.resolve({ secretRef, companyId })).rejects.toThrow(
|
||||
/not referenced by this company's plugin config/i,
|
||||
);
|
||||
expect(mocks.getConfigExactScope).toHaveBeenCalledWith(pluginId, companyId);
|
||||
expect(mocks.resolveSecretValue).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -287,6 +287,14 @@ export function pluginRegistryService(db: Db) {
|
|||
|
||||
// ----- Config ---------------------------------------------------------
|
||||
|
||||
/** Retrieve a plugin's config at the exact requested scope. */
|
||||
getConfigExactScope: (pluginId: string, companyId?: string | null) =>
|
||||
db
|
||||
.select()
|
||||
.from(pluginConfig)
|
||||
.where(pluginConfigExactScopeCondition(pluginId, companyId))
|
||||
.then((rows) => rows[0] ?? null),
|
||||
|
||||
/** Retrieve a plugin's company-scoped config, or the legacy global fallback. */
|
||||
getConfig: async (pluginId: string, companyId?: string | null) => {
|
||||
if (companyId) {
|
||||
|
|
|
|||
|
|
@ -242,7 +242,10 @@ export function createPluginSecretsHandler(
|
|||
throw new Error(PLUGIN_SECRET_REFS_REQUIRE_COMPANY_MESSAGE);
|
||||
}
|
||||
|
||||
const configRow = await registry.getConfig(pluginId, companyId);
|
||||
// Authorize secret refs only from the exact company-scoped config row.
|
||||
// Falling back to legacy/global config would let a live worker keep using
|
||||
// stale global refs after a company-scoped invocation takes over.
|
||||
const configRow = await registry.getConfigExactScope(pluginId, companyId);
|
||||
const refsBySecret = extractSecretRefPathsFromConfig(
|
||||
configRow?.configJson ?? {},
|
||||
manifest?.instanceConfigSchema,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue