Harden production auth startup and dev-mode access

This commit is contained in:
svemagie
2026-03-08 04:01:41 +01:00
parent b72b23ed1c
commit 1558e8b40e
6 changed files with 138 additions and 4 deletions

View File

@@ -0,0 +1,57 @@
import { access, readFile, writeFile } from "node:fs/promises";
const candidates = [
"node_modules/@indiekit/indiekit/lib/indieauth.js",
];
const oldCode = `if (devMode) {
request.session.access_token = process.env.NODE_ENV;
request.session.scope = "create update delete media";
} else if (!process.env.PASSWORD_SECRET) {`;
const newCode = `if (devMode && process.env.INDIEKIT_ALLOW_DEV_AUTH === "1") {
request.session.access_token = process.env.NODE_ENV;
request.session.scope = "create update delete media";
} else if (!process.env.PASSWORD_SECRET) {`;
async function exists(path) {
try {
await access(path);
return true;
} catch {
return false;
}
}
let checked = 0;
let patched = 0;
for (const filePath of candidates) {
if (!(await exists(filePath))) {
continue;
}
checked += 1;
const source = await readFile(filePath, "utf8");
if (source.includes(newCode)) {
continue;
}
if (!source.includes(oldCode)) {
continue;
}
const updated = source.replace(oldCode, newCode);
await writeFile(filePath, updated, "utf8");
patched += 1;
}
if (checked === 0) {
console.log("[postinstall] No indieauth middleware files found");
} else if (patched === 0) {
console.log("[postinstall] indieauth dev-mode guard already patched");
} else {
console.log(`[postinstall] Patched indieauth dev-mode guard in ${patched} file(s)`);
}

View File

@@ -0,0 +1,66 @@
import "dotenv/config";
import bcrypt from "bcrypt";
const strictMode = process.env.REQUIRE_SECURITY !== "0";
const nodeEnv = (process.env.NODE_ENV || "").toLowerCase();
function failOrWarn(message) {
if (strictMode) {
console.error(message);
process.exit(1);
}
console.warn(`${message} Continuing because strict mode is disabled.`);
}
if (nodeEnv !== "production") {
failOrWarn(
`[preflight] NODE_ENV must be "production" for secure startup (received "${process.env.NODE_ENV || "(unset)"}").`,
);
}
if (process.env.INDIEKIT_ALLOW_DEV_AUTH === "1") {
failOrWarn(
"[preflight] INDIEKIT_ALLOW_DEV_AUTH=1 is not allowed in production.",
);
}
const secret = process.env.SECRET || "";
if (secret.length < 32) {
failOrWarn(
"[preflight] SECRET must be set and at least 32 characters long.",
);
}
const passwordSecret = process.env.PASSWORD_SECRET || "";
if (!passwordSecret) {
failOrWarn("[preflight] PASSWORD_SECRET is required.");
}
if (!/^\$2[aby]\$\d{2}\$/.test(passwordSecret)) {
failOrWarn(
"[preflight] PASSWORD_SECRET must be a bcrypt hash (starts with $2a$, $2b$, or $2y$).",
);
}
try {
const emptyPasswordValid = await bcrypt.compare("", passwordSecret);
if (emptyPasswordValid) {
failOrWarn(
"[preflight] PASSWORD_SECRET matches an empty password. Generate a non-empty password hash via /auth/new-password.",
);
}
} catch (error) {
failOrWarn(
`[preflight] PASSWORD_SECRET could not be validated with bcrypt: ${error.message}`,
);
}
if (process.env.INDIEKIT_PASSWORD) {
console.warn(
"[preflight] INDIEKIT_PASSWORD is set but ignored by core auth. Use PASSWORD_SECRET only.",
);
}
console.log("[preflight] Production auth configuration OK");