71 lines
2.2 KiB
TypeScript
71 lines
2.2 KiB
TypeScript
import { URL } from "node:url";
|
|
|
|
interface DestructiveGuardOptions {
|
|
commandName: string;
|
|
allowedDatabaseNames?: string[];
|
|
requireConfirmation?: boolean;
|
|
}
|
|
|
|
const PROTECTED_DATABASE_NAMES = new Set(["capakraken"]);
|
|
|
|
function parseDatabaseUrl(rawUrl: string) {
|
|
const parsed = new URL(rawUrl);
|
|
const databaseName = parsed.pathname.replace(/^\/+/, "");
|
|
|
|
return {
|
|
protocol: parsed.protocol,
|
|
hostname: parsed.hostname,
|
|
port: parsed.port,
|
|
databaseName,
|
|
username: decodeURIComponent(parsed.username),
|
|
};
|
|
}
|
|
|
|
function formatTarget(target: ReturnType<typeof parseDatabaseUrl>) {
|
|
const port = target.port ? `:${target.port}` : "";
|
|
return `${target.protocol}//${target.username}@${target.hostname}${port}/${target.databaseName}`;
|
|
}
|
|
|
|
export function assertDestructiveDbAllowed({
|
|
commandName,
|
|
allowedDatabaseNames = [],
|
|
requireConfirmation = true,
|
|
}: DestructiveGuardOptions) {
|
|
const rawUrl = process.env.DATABASE_URL;
|
|
|
|
if (!rawUrl) {
|
|
throw new Error(`${commandName} aborted: DATABASE_URL is not configured.`);
|
|
}
|
|
|
|
const target = parseDatabaseUrl(rawUrl);
|
|
const allowFlag = process.env.ALLOW_DESTRUCTIVE_DB_TOOLS === "true";
|
|
const confirmationDb = process.env.CONFIRM_DESTRUCTIVE_DB_NAME;
|
|
const allowlisted = allowedDatabaseNames.includes(target.databaseName);
|
|
|
|
if (PROTECTED_DATABASE_NAMES.has(target.databaseName)) {
|
|
throw new Error(
|
|
`${commandName} aborted: database '${target.databaseName}' is explicitly protected from destructive tooling. Target=${formatTarget(target)}`,
|
|
);
|
|
}
|
|
|
|
if (!allowlisted) {
|
|
throw new Error(
|
|
`${commandName} aborted: database '${target.databaseName}' is not in the destructive-tool allowlist (${allowedDatabaseNames.join(", ") || "none"}). Target=${formatTarget(target)}`,
|
|
);
|
|
}
|
|
|
|
if (!allowFlag) {
|
|
throw new Error(
|
|
`${commandName} aborted: set ALLOW_DESTRUCTIVE_DB_TOOLS=true to allow destructive database operations. Target=${formatTarget(target)}`,
|
|
);
|
|
}
|
|
|
|
if (requireConfirmation && confirmationDb !== target.databaseName) {
|
|
throw new Error(
|
|
`${commandName} aborted: set CONFIRM_DESTRUCTIVE_DB_NAME=${target.databaseName} to confirm the destructive target. Current value=${confirmationDb ?? "<unset>"}`,
|
|
);
|
|
}
|
|
|
|
return target;
|
|
}
|