mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-23 00:53:38 +02:00
Add provider vault removal
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
9edac5ca20
commit
33b522aa10
7 files changed
+214
-9
No files matched your search
@@ -14,6 +14,7 @@ const mockSecretService = vi.hoisted(() => ({
|
||||
createProviderConfig: vi.fn(),
|
||||
updateProviderConfig: vi.fn(),
|
||||
disableProviderConfig: vi.fn(),
|
||||
removeProviderConfig: vi.fn(),
|
||||
setDefaultProviderConfig: vi.fn(),
|
||||
checkProviderConfigHealth: vi.fn(),
|
||||
getById: vi.fn(),
|
||||
@@ -344,6 +345,48 @@ describe("secret routes", () => {
|
||||
expect(JSON.stringify(mockLogActivity.mock.calls)).not.toContain("accessKey");
|
||||
});
|
||||
|
||||
it("removes provider vault config locally without deleting remote provider data", async () => {
|
||||
const createdAt = new Date("2026-05-06T00:00:00.000Z");
|
||||
const providerConfig = {
|
||||
id: "11111111-1111-4111-8111-111111111111",
|
||||
companyId: "company-1",
|
||||
provider: "aws_secrets_manager",
|
||||
displayName: "AWS prod",
|
||||
status: "ready",
|
||||
isDefault: false,
|
||||
config: { region: "us-east-1" },
|
||||
healthStatus: null,
|
||||
healthCheckedAt: null,
|
||||
healthMessage: null,
|
||||
healthDetails: null,
|
||||
disabledAt: null,
|
||||
createdByAgentId: null,
|
||||
createdByUserId: "user-1",
|
||||
createdAt,
|
||||
updatedAt: createdAt,
|
||||
};
|
||||
mockSecretService.getProviderConfigById.mockResolvedValue(providerConfig);
|
||||
mockSecretService.removeProviderConfig.mockResolvedValue(providerConfig);
|
||||
|
||||
const res = await request(createApp()).delete(
|
||||
"/api/secret-provider-configs/11111111-1111-4111-8111-111111111111",
|
||||
);
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(mockSecretService.removeProviderConfig).toHaveBeenCalledWith(
|
||||
"11111111-1111-4111-8111-111111111111",
|
||||
);
|
||||
expect(mockSecretService.disableProviderConfig).not.toHaveBeenCalled();
|
||||
expect(mockLogActivity).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({
|
||||
action: "secret_provider_config.removed",
|
||||
details: {
|
||||
provider: "aws_secrets_manager",
|
||||
displayName: "AWS prod",
|
||||
remoteDeleted: false,
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
it("rejects remote import preview for non-board actors", async () => {
|
||||
const res = await request(createApp({
|
||||
type: "agent",
|
||||
|
||||
@@ -492,6 +492,35 @@ describeEmbeddedPostgres("secretService", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("removes provider vault config locally without deleting remote AWS secrets", async () => {
|
||||
const companyId = await seedCompany();
|
||||
const svc = secretService(db);
|
||||
const vault = await svc.createProviderConfig(companyId, {
|
||||
provider: "aws_secrets_manager",
|
||||
displayName: "AWS production",
|
||||
config: { region: "us-east-1", namespace: "prod-use1" },
|
||||
});
|
||||
const secret = await svc.create(companyId, {
|
||||
name: `external-${randomUUID()}`,
|
||||
provider: "aws_secrets_manager",
|
||||
providerConfigId: vault.id,
|
||||
managedMode: "external_reference",
|
||||
externalRef: "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/external",
|
||||
});
|
||||
const deleteSpy = vi.spyOn(awsSecretsManagerProvider, "deleteOrArchive").mockResolvedValue();
|
||||
|
||||
const removed = await svc.removeProviderConfig(vault.id);
|
||||
|
||||
expect(removed?.id).toBe(vault.id);
|
||||
await expect(svc.getProviderConfigById(vault.id)).resolves.toBeNull();
|
||||
const [persistedSecret] = await db
|
||||
.select()
|
||||
.from(companySecrets)
|
||||
.where(eq(companySecrets.id, secret.id));
|
||||
expect(persistedSecret?.providerConfigId).toBeNull();
|
||||
expect(deleteSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("hides soft-deleted secrets and allows name/key reuse", async () => {
|
||||
const companyId = await seedCompany();
|
||||
const svc = secretService(db);
|
||||
|
||||
@@ -172,27 +172,27 @@ export function secretRoutes(db: Db) {
|
||||
}
|
||||
assertCompanyAccess(req, existing.companyId);
|
||||
|
||||
const disabled = await svc.disableProviderConfig(id);
|
||||
if (!disabled) {
|
||||
const removed = await svc.removeProviderConfig(id);
|
||||
if (!removed) {
|
||||
res.status(404).json({ error: "Provider vault not found" });
|
||||
return;
|
||||
}
|
||||
|
||||
await logActivity(db, {
|
||||
companyId: disabled.companyId,
|
||||
companyId: removed.companyId,
|
||||
actorType: "user",
|
||||
actorId: req.actor.userId ?? "board",
|
||||
action: "secret_provider_config.disabled",
|
||||
action: "secret_provider_config.removed",
|
||||
entityType: "secret_provider_config",
|
||||
entityId: disabled.id,
|
||||
entityId: removed.id,
|
||||
details: {
|
||||
provider: disabled.provider,
|
||||
displayName: disabled.displayName,
|
||||
status: disabled.status,
|
||||
provider: removed.provider,
|
||||
displayName: removed.displayName,
|
||||
remoteDeleted: false,
|
||||
},
|
||||
});
|
||||
|
||||
res.json(disabled);
|
||||
res.json(removed);
|
||||
});
|
||||
|
||||
router.post("/secret-provider-configs/:id/default", async (req, res) => {
|
||||
|
||||
@@ -1134,6 +1134,13 @@ export function secretService(db: Db) {
|
||||
.then((rows) => rows[0] ?? null);
|
||||
},
|
||||
|
||||
removeProviderConfig: async (id: string) =>
|
||||
db
|
||||
.delete(companySecretProviderConfigs)
|
||||
.where(eq(companySecretProviderConfigs.id, id))
|
||||
.returning()
|
||||
.then((rows) => rows[0] ?? null),
|
||||
|
||||
setDefaultProviderConfig: async (id: string) => {
|
||||
const existing = await getProviderConfigById(id);
|
||||
if (!existing) return null;
|
||||
|
||||
Reference in new issue
Block a user