2026-03-02 20:58:20 -06:00
|
|
|
import type { ErrorObject } from "ajv";
|
|
|
|
|
import { isKnownSecretTargetId } from "../../secrets/target-registry.js";
|
|
|
|
|
import {
|
|
|
|
|
ErrorCodes,
|
|
|
|
|
errorShape,
|
|
|
|
|
validateSecretsResolveParams,
|
|
|
|
|
validateSecretsResolveResult,
|
|
|
|
|
} from "../protocol/index.js";
|
2026-02-21 13:57:49 -08:00
|
|
|
import type { GatewayRequestHandlers } from "./types.js";
|
|
|
|
|
|
2026-03-02 20:58:20 -06:00
|
|
|
function invalidSecretsResolveField(
|
|
|
|
|
errors: ErrorObject[] | null | undefined,
|
|
|
|
|
): "commandName" | "targetIds" {
|
|
|
|
|
for (const issue of errors ?? []) {
|
|
|
|
|
if (
|
|
|
|
|
issue.instancePath === "/commandName" ||
|
|
|
|
|
(issue.instancePath === "" &&
|
|
|
|
|
String((issue.params as { missingProperty?: unknown })?.missingProperty) === "commandName")
|
|
|
|
|
) {
|
|
|
|
|
return "commandName";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return "targetIds";
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-21 13:57:49 -08:00
|
|
|
export function createSecretsHandlers(params: {
|
|
|
|
|
reloadSecrets: () => Promise<{ warningCount: number }>;
|
2026-03-02 20:58:20 -06:00
|
|
|
resolveSecrets: (params: { commandName: string; targetIds: string[] }) => Promise<{
|
|
|
|
|
assignments: Array<{
|
|
|
|
|
path: string;
|
|
|
|
|
pathSegments: string[];
|
|
|
|
|
value: unknown;
|
|
|
|
|
}>;
|
|
|
|
|
diagnostics: string[];
|
|
|
|
|
inactiveRefPaths: string[];
|
|
|
|
|
}>;
|
2026-02-21 13:57:49 -08:00
|
|
|
}): GatewayRequestHandlers {
|
|
|
|
|
return {
|
|
|
|
|
"secrets.reload": async ({ respond }) => {
|
|
|
|
|
try {
|
|
|
|
|
const result = await params.reloadSecrets();
|
|
|
|
|
respond(true, { ok: true, warningCount: result.warningCount });
|
|
|
|
|
} catch (err) {
|
|
|
|
|
respond(false, undefined, errorShape(ErrorCodes.UNAVAILABLE, String(err)));
|
|
|
|
|
}
|
|
|
|
|
},
|
2026-03-02 20:58:20 -06:00
|
|
|
"secrets.resolve": async ({ params: requestParams, respond }) => {
|
|
|
|
|
if (!validateSecretsResolveParams(requestParams)) {
|
|
|
|
|
const field = invalidSecretsResolveField(validateSecretsResolveParams.errors);
|
|
|
|
|
respond(
|
|
|
|
|
false,
|
|
|
|
|
undefined,
|
|
|
|
|
errorShape(ErrorCodes.INVALID_REQUEST, `invalid secrets.resolve params: ${field}`),
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const commandName = requestParams.commandName.trim();
|
|
|
|
|
if (!commandName) {
|
|
|
|
|
respond(
|
|
|
|
|
false,
|
|
|
|
|
undefined,
|
|
|
|
|
errorShape(ErrorCodes.INVALID_REQUEST, "invalid secrets.resolve params: commandName"),
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const targetIds = requestParams.targetIds
|
|
|
|
|
.map((entry) => entry.trim())
|
|
|
|
|
.filter((entry) => entry.length > 0);
|
|
|
|
|
|
|
|
|
|
for (const targetId of targetIds) {
|
|
|
|
|
if (!isKnownSecretTargetId(targetId)) {
|
|
|
|
|
respond(
|
|
|
|
|
false,
|
|
|
|
|
undefined,
|
|
|
|
|
errorShape(
|
|
|
|
|
ErrorCodes.INVALID_REQUEST,
|
|
|
|
|
`invalid secrets.resolve params: unknown target id "${String(targetId)}"`,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const result = await params.resolveSecrets({
|
|
|
|
|
commandName,
|
|
|
|
|
targetIds,
|
|
|
|
|
});
|
|
|
|
|
const payload = {
|
|
|
|
|
ok: true,
|
|
|
|
|
assignments: result.assignments,
|
|
|
|
|
diagnostics: result.diagnostics,
|
|
|
|
|
inactiveRefPaths: result.inactiveRefPaths,
|
|
|
|
|
};
|
|
|
|
|
if (!validateSecretsResolveResult(payload)) {
|
|
|
|
|
throw new Error("secrets.resolve returned invalid payload.");
|
|
|
|
|
}
|
|
|
|
|
respond(true, payload);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
respond(false, undefined, errorShape(ErrorCodes.UNAVAILABLE, String(err)));
|
|
|
|
|
}
|
|
|
|
|
},
|
2026-02-21 13:57:49 -08:00
|
|
|
};
|
|
|
|
|
}
|