56 lines
1.9 KiB
JavaScript
56 lines
1.9 KiB
JavaScript
const SAFE_INTERNAL_HOSTS = new Set(["web", "acai-web", "localhost", "127.0.0.1"]);
|
|
|
|
function parseUrl(url, fieldName, context) {
|
|
try {
|
|
return new URL(url);
|
|
} catch {
|
|
throw new Error(`[${context}] Invalid ${fieldName}: ${url || "<empty>"}`);
|
|
}
|
|
}
|
|
|
|
export function assertSafeCmsTarget(target, context = "cms") {
|
|
const publicUrl = typeof target === "string" ? target : (target?.web_url || "");
|
|
const apiUrl = typeof target === "string" ? target : (target?.api_web_url || "");
|
|
|
|
if (!apiUrl) {
|
|
throw new Error(
|
|
`[${context}] ACAI_API_WEB_URL is required. Refusing to use ACAI_WEB_URL directly for CMS requests.`
|
|
);
|
|
}
|
|
|
|
const parsedApiUrl = parseUrl(apiUrl, "ACAI_API_WEB_URL", context);
|
|
if (!SAFE_INTERNAL_HOSTS.has(parsedApiUrl.hostname)) {
|
|
throw new Error(
|
|
`[${context}] Unsafe ACAI_API_WEB_URL host "${parsedApiUrl.hostname}". ` +
|
|
`Only approved local hosts are allowed: ${Array.from(SAFE_INTERNAL_HOSTS).join(", ")}.`
|
|
);
|
|
}
|
|
|
|
if (!["http:", "https:"].includes(parsedApiUrl.protocol)) {
|
|
throw new Error(
|
|
`[${context}] Unsafe ACAI_API_WEB_URL protocol "${parsedApiUrl.protocol}".`
|
|
);
|
|
}
|
|
|
|
if (publicUrl) {
|
|
const parsedPublicUrl = parseUrl(publicUrl, "ACAI_WEB_URL", context);
|
|
const publicIsSafeInternal = SAFE_INTERNAL_HOSTS.has(parsedPublicUrl.hostname);
|
|
if (!publicIsSafeInternal && parsedPublicUrl.host === parsedApiUrl.host) {
|
|
throw new Error(
|
|
`[${context}] ACAI_API_WEB_URL resolves to the same public host as ACAI_WEB_URL (${parsedApiUrl.host}).`
|
|
);
|
|
}
|
|
}
|
|
|
|
return {
|
|
publicUrl,
|
|
apiUrl,
|
|
forgeHost: typeof target === "string" ? null : (target?.forge_host || null),
|
|
};
|
|
}
|
|
|
|
export function isSafeInternalHost(hostname) {
|
|
return SAFE_INTERNAL_HOSTS.has(hostname);
|
|
}
|
|
|