mirror of
https://github.com/goauthentik/authentik
synced 2026-05-14 10:56:52 +02:00
* core: add .npmrc baseline to block dependency lifecycle scripts Set ignore-scripts=true at the repo root, plus engine-strict, save-exact, audit, and prefer-offline. This neutralizes the dominant npm supply-chain attack vector — postinstall scripts in transitive dependencies — at the cost of requiring an explicit rebuild for the handful of packages that legitimately need install scripts (esbuild, chromedriver, tree-sitter, tree-sitter-json). The next commit wires that rebuild into the Makefile. Co-Authored-By: Playpen Agent <279763771+playpen-agent@users.noreply.github.com> * core: route node installs through make to retire website preinstall hook Make docs-install depend on a new root-node-install so the root deps are guaranteed before the website install runs, removing the need for the website/preinstall lifecycle script. Rebuild the small audited list of trusted packages (esbuild, chromedriver, tree-sitter, tree-sitter-json) after the web install so ignore-scripts=true remains the only path that needs maintenance. web/README documents the new workflow. Co-Authored-By: Playpen Agent <279763771+playpen-agent@users.noreply.github.com> * Clean up install scripts. * Track .npmrc in CODEOWNERS * Fix formatter config. Reformat. * Fix mounted references. * Flesh out node scripts. * Bump engines. * Prep containers. * Update makefile. * Flesh out github actions. * Clean up docs container. * lint. Bump. Lint. Bump NPM version. * Add limits. * collapse the composite's three setup-node calls to one cache restore * Add SHA. * Bump NPM range. * Run formatter. * Bump NPM. * Remove extra install. * Fix website deps. * Use local prettier. Fix drift in CI. * ci: build frontend in CI with node_env production Signed-off-by: Jens Langhammer <jens@goauthentik.io> * Install docusaurus config. * Fix linter warning, order. * Add linter commands. * Add timeout. * Remove pre install check. --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io> Co-authored-by: Playpen Agent <279763771+playpen-agent@users.noreply.github.com> Co-authored-by: Jens Langhammer <jens@goauthentik.io>
166 lines
5.5 KiB
JavaScript
166 lines
5.5 KiB
JavaScript
import * as crypto from "node:crypto";
|
|
import * as fs from "node:fs/promises";
|
|
import { join, relative } from "node:path";
|
|
|
|
import { ConsoleLogger } from "../../../packages/logger-js/lib/node.js";
|
|
import { $ } from "./commands.mjs";
|
|
|
|
const REGISTRY_BASE_URL = "https://registry.npmjs.org";
|
|
const REGISTRY_URL = `${REGISTRY_BASE_URL}/corepack`;
|
|
const OUTPUT_DIR = join(".corepack", "releases");
|
|
const OUTPUT_FILENAME = "latest.tgz";
|
|
|
|
export const corepack = $.bind("corepack");
|
|
|
|
/**
|
|
* Reads the installed Corepack version.
|
|
*
|
|
* @param {string} [cwd] The directory to run the command in.
|
|
* @returns {Promise<string | null>} The installed Corepack version
|
|
*/
|
|
export function readCorepackVersion(cwd = process.cwd()) {
|
|
return $`corepack --version`({ cwd });
|
|
}
|
|
|
|
const logger = ConsoleLogger.prefix("setup-corepack");
|
|
|
|
/**
|
|
* @param {string} baseDirectory
|
|
*/
|
|
export async function pullLatestCorepack(baseDirectory = process.cwd()) {
|
|
logger.info("Fetching corepack metadata from registry...");
|
|
|
|
const outputDir = join(baseDirectory, OUTPUT_DIR);
|
|
const outputPath = join(outputDir, OUTPUT_FILENAME);
|
|
|
|
const res = await fetch(REGISTRY_URL, { signal: AbortSignal.timeout(1000 * 60) });
|
|
|
|
if (!res.ok) {
|
|
throw new Error(`Failed to fetch registry metadata: ${res.status} ${res.statusText}`);
|
|
}
|
|
|
|
const metadata = await res.json();
|
|
|
|
const latestVersion = metadata["dist-tags"].latest;
|
|
const versionData = metadata.versions[latestVersion];
|
|
const tarballUrl = versionData.dist.tarball;
|
|
const expectedIntegrity = versionData.dist.integrity;
|
|
|
|
logger.info(`Latest corepack version: ${latestVersion}`);
|
|
logger.info(`Tarball URL: ${tarballUrl}`);
|
|
logger.info(`Expected integrity: ${expectedIntegrity}`);
|
|
|
|
logger.info({ url: tarballUrl }, "Downloading tarball...");
|
|
|
|
const tarballRes = await fetch(tarballUrl, {
|
|
signal: AbortSignal.timeout(1000 * 60),
|
|
});
|
|
|
|
if (!tarballRes.ok) {
|
|
throw new Error(
|
|
`Failed to download tarball: ${tarballRes.status} ${tarballRes.statusText}`,
|
|
);
|
|
}
|
|
|
|
const tarballBuffer = Buffer.from(await tarballRes.arrayBuffer());
|
|
|
|
logger.info("Verifying integrity...");
|
|
|
|
const [algorithm, expectedHash] = expectedIntegrity.split("-");
|
|
const actualHash = crypto.createHash(algorithm).update(tarballBuffer).digest("base64");
|
|
|
|
if (actualHash !== expectedHash) {
|
|
throw new Error(
|
|
`Integrity mismatch!\n Expected: ${expectedHash}\n Actual: ${actualHash}`,
|
|
);
|
|
}
|
|
|
|
logger.info("Integrity verified.");
|
|
|
|
await fs.mkdir(outputDir, { recursive: true });
|
|
await fs.writeFile(outputPath, tarballBuffer);
|
|
|
|
logger.info(`Saved to ${relative(baseDirectory, outputPath)}`);
|
|
logger.info(`corepack@${latestVersion} (${expectedIntegrity})`);
|
|
}
|
|
|
|
/**
|
|
* Downloads the tarball for a package manager spec from the npm registry and
|
|
* verifies it matches the expected Corepack-style checksum (`<algorithm>.<hex>`).
|
|
*
|
|
* Corepack's CLI does not enforce the `+integrity` suffix on the `packageManager`
|
|
* field when invoked via `corepack install -g`, so we verify the bytes ourselves
|
|
* before handing control back to Corepack.
|
|
*
|
|
* @param {string} packageManager E.g. `npm@11.14.1`.
|
|
* @param {string} expectedChecksum E.g. `sha512.6a8a4d67...`.
|
|
* @returns {Promise<void>}
|
|
*/
|
|
export async function verifyPackageManagerIntegrity(packageManager, expectedChecksum) {
|
|
const atIndex = packageManager.lastIndexOf("@");
|
|
|
|
if (atIndex <= 0) {
|
|
throw new Error(
|
|
`Invalid packageManager spec "${packageManager}". Expected "name@version".`,
|
|
);
|
|
}
|
|
|
|
const name = packageManager.slice(0, atIndex);
|
|
const version = packageManager.slice(atIndex + 1);
|
|
|
|
const separatorIndex = expectedChecksum.indexOf(".");
|
|
|
|
if (separatorIndex <= 0) {
|
|
throw new Error(
|
|
`Invalid checksum format "${expectedChecksum}". Expected "<algorithm>.<hex>".`,
|
|
);
|
|
}
|
|
|
|
const algorithm = expectedChecksum.slice(0, separatorIndex);
|
|
const expectedHex = expectedChecksum.slice(separatorIndex + 1).toLowerCase();
|
|
|
|
logger.info(`Verifying ${name}@${version} against ${algorithm} checksum...`);
|
|
|
|
const metadataUrl = `${REGISTRY_BASE_URL}/${encodeURIComponent(name)}/${encodeURIComponent(version)}`;
|
|
|
|
const metadataRes = await fetch(metadataUrl, {
|
|
signal: AbortSignal.timeout(1000 * 60),
|
|
});
|
|
|
|
if (!metadataRes.ok) {
|
|
throw new Error(
|
|
`Failed to fetch registry metadata for ${name}@${version}: ${metadataRes.status} ${metadataRes.statusText}`,
|
|
);
|
|
}
|
|
|
|
const versionData = await metadataRes.json();
|
|
const tarballUrl = versionData?.dist?.tarball;
|
|
|
|
if (!tarballUrl) {
|
|
throw new Error(`Registry response missing dist.tarball for ${name}@${version}.`);
|
|
}
|
|
|
|
logger.info({ url: tarballUrl }, "Downloading tarball...");
|
|
|
|
const tarballRes = await fetch(tarballUrl, {
|
|
signal: AbortSignal.timeout(1000 * 60),
|
|
});
|
|
|
|
if (!tarballRes.ok) {
|
|
throw new Error(
|
|
`Failed to download tarball: ${tarballRes.status} ${tarballRes.statusText}`,
|
|
);
|
|
}
|
|
|
|
const tarballBuffer = Buffer.from(await tarballRes.arrayBuffer());
|
|
const actualHex = crypto.createHash(algorithm).update(tarballBuffer).digest("hex");
|
|
|
|
if (actualHex !== expectedHex) {
|
|
throw new Error(
|
|
`Integrity mismatch for ${name}@${version}!\n Expected: ${algorithm}.${expectedHex}\n Actual: ${algorithm}.${actualHex}`,
|
|
);
|
|
}
|
|
|
|
logger.info(`Integrity verified for ${name}@${version}.`);
|
|
}
|