mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Updates the vendored widget to BitLogin 205b067, which includes the second-audit hardening the previous bytes predated -- phrase-gated sudo key, deterministic migration, the pinned derivation chain -- plus the entropy work: the closed nonce/IV surface, fail-closed RNG tests, and the package-wide Math.random ban. Also ports the provenance recording from BitUnlock's vendoring script. The manifest now carries repository, commit, commit date and toolchain, and the script refuses to vendor from a BitLogin checkout with uncommitted changes, resolved before vendorDir is deleted so the refusal leaves the committed files intact. Hashes prove the bytes have not changed since they were committed; only the commit says which BitLogin produced them, and version "0.1.0" has covered many substantive changes. EXISTING SIGN-INS ARE UNAFFECTED, which matters most here since this is the one consumer with real users. normalize.ts, argon2id.ts, hkdf.ts and scalarExpand.ts are byte-identical between the previously vendored build and 205b067, so login name + password derive the same locator, signing scalar and capsule key as before. Accounts created with a hand-typed password still sign in: the manual-password removal took away the creation, recovery and rotation CONTROLS, not the acceptance, and nothing on the login path inspects the password's shape. BitLogin's legacyPassword.e2e.test.ts pins that, including the migration path of signing in with an old typed password and rotating onto a generated one. What does change for users: creating a new account, recovering, or rotating now hands out a generated six-word passphrase instead of accepting a typed one. 2354 unit tests pass, including the 10 bitlogin-specific ones in auth-providers-bitlogin and bitlogin-modal-integration. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01HtNMsKzzFTPURemX7tT19u
106 lines
4.9 KiB
JavaScript
106 lines
4.9 KiB
JavaScript
// Vendor the sibling @bitlogin/widget build into a pinned, same-origin bundle.
|
|
//
|
|
// bitvid's CSP is script-src 'self' (plus a couple of pinned CDN fallbacks for
|
|
// nostr-tools) -- the same reasoning as the existing bitcoin-connect/blossom-sdk
|
|
// vendor steps. Unlike those, BitLogin ships its own Vite-built ESM output
|
|
// directly (bitlogin.js + cryptoWorker.js + a shared chunk they both import),
|
|
// so this script copies that output verbatim instead of re-bundling it with
|
|
// esbuild -- the worker's relative self-URL resolution depends on the three
|
|
// files staying exactly as built and sitting side by side (see BitRoad's and
|
|
// BitUnlock's identical vendor steps for this same reasoning).
|
|
//
|
|
// node scripts/build-bitlogin-widget.mjs
|
|
// BITLOGIN_ENTRY=/path/to/bitlogin node scripts/build-bitlogin-widget.mjs
|
|
|
|
import { createHash } from "node:crypto";
|
|
import { copyFile, mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
import { existsSync } from "node:fs";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { execSync } from "node:child_process";
|
|
|
|
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
const bitloginRoot = process.env.BITLOGIN_ENTRY
|
|
? path.resolve(process.env.BITLOGIN_ENTRY)
|
|
: path.resolve(repoRoot, "../bitlogin");
|
|
const widgetDir = path.join(bitloginRoot, "packages/widget");
|
|
const widgetDist = path.join(widgetDir, "dist");
|
|
const vendorDir = path.join(repoRoot, "vendor/bitlogin");
|
|
const integrityFile = path.join(repoRoot, "vendor/bitlogin.integrity.json");
|
|
|
|
const packageJson = JSON.parse(await readFile(path.join(widgetDir, "package.json"), "utf8"));
|
|
if (packageJson.name !== "@bitlogin/widget") {
|
|
throw new Error("BITLOGIN_ENTRY must point to a BitLogin checkout (packages/widget/package.json not found there).");
|
|
}
|
|
|
|
// Provenance (BitUnlock entropy audit M1, ported). Hashes prove the vendored
|
|
// bytes have not changed since they were committed; they cannot say WHICH
|
|
// BitLogin commit produced them. @bitlogin/widget is a private package pinned
|
|
// at 0.1.0 across many substantive changes, so the version string distinguishes
|
|
// nothing. Resolved BEFORE the dist build and before vendorDir is deleted, so
|
|
// refusing a dirty checkout leaves the committed vendored files untouched.
|
|
const git = (args) => execSync(`git ${args}`, { cwd: bitloginRoot, encoding: "utf8" }).trim();
|
|
|
|
let source;
|
|
try {
|
|
const dirty = git("status --porcelain") !== "";
|
|
if (dirty && process.env.BITLOGIN_ALLOW_DIRTY !== "1") {
|
|
throw new Error(
|
|
"DIRTY_CHECKOUT: the BitLogin checkout has uncommitted changes, so the artifact this "
|
|
+ "would produce corresponds to no commit and cannot be reproduced. Commit there first, "
|
|
+ "or set BITLOGIN_ALLOW_DIRTY=1 for a throwaway local build.",
|
|
);
|
|
}
|
|
source = {
|
|
repository: git("remote get-url origin"),
|
|
commit: git("rev-parse HEAD"),
|
|
commitDate: git("log -1 --format=%cI"),
|
|
dirty,
|
|
provenance: dirty ? "dirty" : "attributed",
|
|
toolchain: { node: process.version, vite: packageJson.devDependencies?.vite },
|
|
};
|
|
} catch (error) {
|
|
if (error.message.startsWith("DIRTY_CHECKOUT:")) {
|
|
throw new Error(error.message.replace("DIRTY_CHECKOUT: ", ""));
|
|
}
|
|
throw new Error(
|
|
`Could not read BitLogin git provenance from ${bitloginRoot}: ${error.message}. `
|
|
+ "Vendoring from a non-git source leaves the artifact unattributable; refusing.",
|
|
);
|
|
}
|
|
console.log(`[vendor-bitlogin] vendoring @bitlogin/widget from ${source.commit.slice(0, 12)}`);
|
|
|
|
if (!existsSync(widgetDist)) {
|
|
console.log("[build-bitlogin-widget] dist missing, building @bitlogin/core then @bitlogin/widget first...");
|
|
execSync("npm run build -w @bitlogin/core", { cwd: bitloginRoot, stdio: "inherit" });
|
|
execSync("npm run build -w @bitlogin/widget", { cwd: bitloginRoot, stdio: "inherit" });
|
|
}
|
|
|
|
await rm(vendorDir, { recursive: true, force: true });
|
|
await mkdir(vendorDir, { recursive: true });
|
|
|
|
const distEntries = (await readdir(widgetDist)).filter((name) => name.endsWith(".js"));
|
|
if (!distEntries.includes("bitlogin.js") || !distEntries.includes("cryptoWorker.js")) {
|
|
throw new Error("Expected bitlogin.js and cryptoWorker.js in @bitlogin/widget dist -- did the build layout change?");
|
|
}
|
|
|
|
const files = {};
|
|
for (const name of distEntries) {
|
|
await copyFile(path.join(widgetDist, name), path.join(vendorDir, name));
|
|
const bytes = await readFile(path.join(widgetDist, name));
|
|
files[name] = { sha256: createHash("sha256").update(bytes).digest("hex"), bytes: bytes.byteLength };
|
|
}
|
|
|
|
await writeFile(
|
|
integrityFile,
|
|
`${JSON.stringify({ package: packageJson.name, version: packageJson.version, source, files }, null, 2)}\n`,
|
|
);
|
|
|
|
try {
|
|
await copyFile(path.join(bitloginRoot, "LICENSE"), path.join(repoRoot, "vendor/bitlogin.LICENSE"));
|
|
} catch (error) {
|
|
console.warn("[build-bitlogin-widget] could not copy LICENSE:", error.message);
|
|
}
|
|
|
|
console.log("[build-bitlogin-widget] wrote", path.relative(repoRoot, vendorDir), `(${distEntries.length} files)`);
|