156 lines
4.1 KiB
TypeScript
156 lines
4.1 KiB
TypeScript
import {
|
|
ensureExecApprovals,
|
|
normalizeExecApprovals,
|
|
readExecApprovalsSnapshot,
|
|
resolveExecApprovalsSocketPath,
|
|
saveExecApprovals,
|
|
type ExecApprovalsFile,
|
|
type ExecApprovalsSnapshot,
|
|
} from "../../infra/exec-approvals.js";
|
|
import {
|
|
ErrorCodes,
|
|
errorShape,
|
|
formatValidationErrors,
|
|
validateExecApprovalsGetParams,
|
|
validateExecApprovalsSetParams,
|
|
} from "../protocol/index.js";
|
|
import type { GatewayRequestHandlers, RespondFn } from "./types.js";
|
|
|
|
function resolveBaseHash(params: unknown): string | null {
|
|
const raw = (params as { baseHash?: unknown })?.baseHash;
|
|
if (typeof raw !== "string") return null;
|
|
const trimmed = raw.trim();
|
|
return trimmed ? trimmed : null;
|
|
}
|
|
|
|
function requireApprovalsBaseHash(
|
|
params: unknown,
|
|
snapshot: ExecApprovalsSnapshot,
|
|
respond: RespondFn,
|
|
): boolean {
|
|
if (!snapshot.exists) return true;
|
|
if (!snapshot.hash) {
|
|
respond(
|
|
false,
|
|
undefined,
|
|
errorShape(
|
|
ErrorCodes.INVALID_REQUEST,
|
|
"exec approvals base hash unavailable; re-run exec.approvals.get and retry",
|
|
),
|
|
);
|
|
return false;
|
|
}
|
|
const baseHash = resolveBaseHash(params);
|
|
if (!baseHash) {
|
|
respond(
|
|
false,
|
|
undefined,
|
|
errorShape(
|
|
ErrorCodes.INVALID_REQUEST,
|
|
"exec approvals base hash required; re-run exec.approvals.get and retry",
|
|
),
|
|
);
|
|
return false;
|
|
}
|
|
if (baseHash !== snapshot.hash) {
|
|
respond(
|
|
false,
|
|
undefined,
|
|
errorShape(
|
|
ErrorCodes.INVALID_REQUEST,
|
|
"exec approvals changed since last load; re-run exec.approvals.get and retry",
|
|
),
|
|
);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function redactExecApprovals(file: ExecApprovalsFile): ExecApprovalsFile {
|
|
const socketPath = file.socket?.path?.trim();
|
|
return {
|
|
...file,
|
|
socket: socketPath ? { path: socketPath } : undefined,
|
|
};
|
|
}
|
|
|
|
export const execApprovalsHandlers: GatewayRequestHandlers = {
|
|
"exec.approvals.get": ({ params, respond }) => {
|
|
if (!validateExecApprovalsGetParams(params)) {
|
|
respond(
|
|
false,
|
|
undefined,
|
|
errorShape(
|
|
ErrorCodes.INVALID_REQUEST,
|
|
`invalid exec.approvals.get params: ${formatValidationErrors(validateExecApprovalsGetParams.errors)}`,
|
|
),
|
|
);
|
|
return;
|
|
}
|
|
ensureExecApprovals();
|
|
const snapshot = readExecApprovalsSnapshot();
|
|
respond(
|
|
true,
|
|
{
|
|
path: snapshot.path,
|
|
exists: snapshot.exists,
|
|
hash: snapshot.hash,
|
|
file: redactExecApprovals(snapshot.file),
|
|
},
|
|
undefined,
|
|
);
|
|
},
|
|
"exec.approvals.set": ({ params, respond }) => {
|
|
if (!validateExecApprovalsSetParams(params)) {
|
|
respond(
|
|
false,
|
|
undefined,
|
|
errorShape(
|
|
ErrorCodes.INVALID_REQUEST,
|
|
`invalid exec.approvals.set params: ${formatValidationErrors(validateExecApprovalsSetParams.errors)}`,
|
|
),
|
|
);
|
|
return;
|
|
}
|
|
ensureExecApprovals();
|
|
const snapshot = readExecApprovalsSnapshot();
|
|
if (!requireApprovalsBaseHash(params, snapshot, respond)) {
|
|
return;
|
|
}
|
|
const incoming = (params as { file?: unknown }).file;
|
|
if (!incoming || typeof incoming !== "object") {
|
|
respond(
|
|
false,
|
|
undefined,
|
|
errorShape(ErrorCodes.INVALID_REQUEST, "exec approvals file is required"),
|
|
);
|
|
return;
|
|
}
|
|
const normalized = normalizeExecApprovals(incoming as ExecApprovalsFile);
|
|
const currentSocketPath = snapshot.file.socket?.path?.trim();
|
|
const currentToken = snapshot.file.socket?.token?.trim();
|
|
const socketPath =
|
|
normalized.socket?.path?.trim() ?? currentSocketPath ?? resolveExecApprovalsSocketPath();
|
|
const token = normalized.socket?.token?.trim() ?? currentToken ?? "";
|
|
const next: ExecApprovalsFile = {
|
|
...normalized,
|
|
socket: {
|
|
path: socketPath,
|
|
token,
|
|
},
|
|
};
|
|
saveExecApprovals(next);
|
|
const nextSnapshot = readExecApprovalsSnapshot();
|
|
respond(
|
|
true,
|
|
{
|
|
path: nextSnapshot.path,
|
|
exists: nextSnapshot.exists,
|
|
hash: nextSnapshot.hash,
|
|
file: redactExecApprovals(nextSnapshot.file),
|
|
},
|
|
undefined,
|
|
);
|
|
},
|
|
};
|