forked from farhoodlabs/paperclip
revert(secrets): drop fork's usages-tracking + delete guard
Now that upstream's #5429 provides provider-based secrets management with formal bindings (companySecretBindings table populated at config-save time) plus the /secrets/:id/usage endpoint backed by listBindingReferences(), the fork's parallel usages() scan is redundant for agent/routine bindings. The fork's scan did cover one path upstream doesn't track: skill metadata sourceAuthSecretId references. Dropping this means accidental deletion of a skill's auth secret is no longer rejected — accepted as a chase-upstream tradeoff. - server/src/services/secrets.ts: drop usages(), SecretUsage* types, in-use guard in remove(), and companySkills/agentService imports - server/src/routes/secrets.ts: drop GET /secrets/:id/usages route - ui/src/api/secrets.ts: drop usages() client method Typechecks clean on server and ui.
This commit is contained in:
@@ -419,19 +419,6 @@ export function secretRoutes(db: Db) {
|
||||
res.json(updated);
|
||||
});
|
||||
|
||||
router.get("/secrets/:id/usages", async (req, res) => {
|
||||
assertBoard(req);
|
||||
const id = req.params.id as string;
|
||||
const existing = await svc.getById(id);
|
||||
if (!existing) {
|
||||
res.status(404).json({ error: "Secret not found" });
|
||||
return;
|
||||
}
|
||||
assertCompanyAccess(req, existing.companyId);
|
||||
const used = await svc.usages(existing.companyId, id);
|
||||
res.json({ agents: used.agents, skills: used.skills });
|
||||
});
|
||||
|
||||
router.get("/secrets/:id/usage", async (req, res) => {
|
||||
assertBoard(req);
|
||||
const id = req.params.id as string;
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
companySecretProviderConfigs,
|
||||
companySecrets,
|
||||
companySecretVersions,
|
||||
companySkills,
|
||||
environments,
|
||||
heartbeatRuns,
|
||||
issues,
|
||||
@@ -53,7 +52,6 @@ import type {
|
||||
SecretProviderWriteContext,
|
||||
} from "../secrets/types.js";
|
||||
import { isSecretProviderClientError } from "../secrets/types.js";
|
||||
import { agentService } from "./agents.js";
|
||||
|
||||
const ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
||||
const SENSITIVE_ENV_KEY_RE =
|
||||
@@ -261,10 +259,6 @@ export function secretService(db: Db) {
|
||||
fieldPath?: string;
|
||||
};
|
||||
|
||||
type SecretUsageAgent = { id: string; name: string; envKeys: string[] };
|
||||
type SecretUsageSkill = { id: string; name: string; slug: string };
|
||||
type SecretUsages = { agents: SecretUsageAgent[]; skills: SecretUsageSkill[] };
|
||||
|
||||
async function getById(id: string) {
|
||||
return db
|
||||
.select()
|
||||
@@ -273,48 +267,6 @@ export function secretService(db: Db) {
|
||||
.then((rows) => rows[0] ?? null);
|
||||
}
|
||||
|
||||
async function usages(companyId: string, secretId: string): Promise<SecretUsages> {
|
||||
const agents = agentService(db);
|
||||
const allAgents = await agents.list(companyId);
|
||||
const agentRefs: SecretUsageAgent[] = [];
|
||||
for (const agent of allAgents) {
|
||||
const config = asRecord(agent.adapterConfig);
|
||||
if (!config) continue;
|
||||
const env = asRecord(config.env);
|
||||
if (!env) continue;
|
||||
const matchingKeys: string[] = [];
|
||||
for (const [key, rawBinding] of Object.entries(env)) {
|
||||
const binding = asRecord(rawBinding);
|
||||
if (!binding) continue;
|
||||
if (binding.type === "secret_ref" && binding.secretId === secretId) {
|
||||
matchingKeys.push(key);
|
||||
}
|
||||
}
|
||||
if (matchingKeys.length > 0) {
|
||||
agentRefs.push({ id: agent.id, name: agent.name, envKeys: matchingKeys });
|
||||
}
|
||||
}
|
||||
|
||||
const skillRows = await db
|
||||
.select({
|
||||
id: companySkills.id,
|
||||
name: companySkills.name,
|
||||
slug: companySkills.slug,
|
||||
})
|
||||
.from(companySkills)
|
||||
.where(and(
|
||||
eq(companySkills.companyId, companyId),
|
||||
sql`${companySkills.metadata} ->> 'sourceAuthSecretId' = ${secretId}`,
|
||||
));
|
||||
const skillRefs: SecretUsageSkill[] = skillRows.map((row) => ({
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
slug: row.slug,
|
||||
}));
|
||||
|
||||
return { agents: agentRefs, skills: skillRefs };
|
||||
}
|
||||
|
||||
async function getByName(companyId: string, name: string) {
|
||||
return db
|
||||
.select()
|
||||
@@ -2083,16 +2035,6 @@ export function secretService(db: Db) {
|
||||
remove: async (secretId: string) => {
|
||||
const secret = await getById(secretId);
|
||||
if (!secret) return null;
|
||||
const used = await usages(secret.companyId, secretId);
|
||||
if (used.agents.length > 0 || used.skills.length > 0) {
|
||||
const agentNames = used.agents.map((agent) => agent.name);
|
||||
const skillNames = used.skills.map((skill) => skill.name);
|
||||
const names = [...agentNames, ...skillNames].sort((left, right) => left.localeCompare(right));
|
||||
throw unprocessable(
|
||||
`Cannot delete secret "${secret.name}" while it is still used by ${names.join(", ")}. Detach it from those references first.`,
|
||||
{ secretId, usedByAgents: used.agents, usedBySkills: used.skills },
|
||||
);
|
||||
}
|
||||
const versionRow = await getSecretVersion(secret.id, secret.latestVersion);
|
||||
const providerId = secret.provider as SecretProvider;
|
||||
const provider = getSecretProvider(providerId);
|
||||
@@ -2139,8 +2081,6 @@ export function secretService(db: Db) {
|
||||
return secret;
|
||||
},
|
||||
|
||||
usages,
|
||||
|
||||
normalizeAdapterConfigForPersistence: async (
|
||||
companyId: string,
|
||||
adapterConfig: Record<string, unknown>,
|
||||
|
||||
@@ -126,11 +126,6 @@ export const secretsApi = {
|
||||
archive: (id: string) =>
|
||||
api.patch<CompanySecret>(`/secrets/${id}`, { status: "archived" satisfies SecretStatus }),
|
||||
remove: (id: string) => api.delete<{ ok: true }>(`/secrets/${id}`),
|
||||
usages: (id: string) =>
|
||||
api.get<{
|
||||
agents: { id: string; name: string; envKeys: string[] }[];
|
||||
skills: { id: string; name: string; slug: string }[];
|
||||
}>(`/secrets/${id}/usages`),
|
||||
usage: (id: string) => api.get<SecretUsageResponse>(`/secrets/${id}/usage`),
|
||||
accessEvents: (id: string) => api.get<SecretAccessEvent[]>(`/secrets/${id}/access-events`),
|
||||
remoteImportPreview: (companyId: string, data: RemoteImportPreviewInput) =>
|
||||
|
||||
Reference in New Issue
Block a user