Add safe password setup recovery mode
This commit is contained in:
2
.github/workflows/deploy.yml
vendored
2
.github/workflows/deploy.yml
vendored
@@ -43,7 +43,7 @@ jobs:
|
||||
|
||||
# Ensure env file exists and contains auth secrets required by start.sh.
|
||||
sudo bastille cmd node sh -lc 'su -l indiekit -c "cd /usr/local/indiekit && test -f .env"'
|
||||
sudo bastille cmd node sh -lc 'su -l indiekit -c "cd /usr/local/indiekit && if ! (grep -Eq \"^SECRET=.*\" .env && grep -Eq \"^PASSWORD_SECRET=.*\" .env); then echo \"Missing SECRET or PASSWORD_SECRET in /usr/local/indiekit/.env\"; exit 1; fi"'
|
||||
sudo bastille cmd node sh -lc 'su -l indiekit -c "cd /usr/local/indiekit && if ! grep -Eq \"^SECRET=.*\" .env; then echo \"Missing SECRET in /usr/local/indiekit/.env\"; exit 1; fi; if ! (grep -Eq \"^PASSWORD_SECRET=.*\" .env || grep -Eq \"^INDIEKIT_ALLOW_PASSWORD_SETUP=1\" .env); then echo \"Missing PASSWORD_SECRET (or set INDIEKIT_ALLOW_PASSWORD_SETUP=1 for one-time recovery) in /usr/local/indiekit/.env\"; exit 1; fi"'
|
||||
|
||||
# Validate startup prerequisites before touching the running service.
|
||||
sudo bastille cmd node sh -lc 'su -l indiekit -c "cd /usr/local/indiekit && NODE_ENV=production node scripts/preflight-production-security.mjs"'
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
- When `INDIEKIT_ADMIN_URL` is set, config wires absolute auth endpoints/callback base (`/auth`, `/auth/token`, `/auth/introspect`) to that URL to keep login redirects on `/admin/*`.
|
||||
- Login uses `PASSWORD_SECRET` (bcrypt hash), not `INDIEKIT_PASSWORD`.
|
||||
- If no `PASSWORD_SECRET` exists yet, open `/admin/auth/new-password` once to generate it.
|
||||
- If login is blocked because `PASSWORD_SECRET` is missing/invalid, set `INDIEKIT_ALLOW_PASSWORD_SETUP=1` temporarily, restart, generate a new hash via `/admin/auth/new-password`, set `PASSWORD_SECRET` to that hash, then remove `INDIEKIT_ALLOW_PASSWORD_SETUP`.
|
||||
- If login appears passwordless, first check for an existing authenticated session cookie. Use `/session/logout` (or `/admin/session/logout` behind proxy) to force a fresh login challenge.
|
||||
- Upstream IndieKit auto-authenticates in dev mode (`NODE_ENV=development`). This repository patches that behavior so dev auto-auth only works when `INDIEKIT_ALLOW_DEV_AUTH=1` is explicitly set.
|
||||
- Production startup now fails closed when auth/session settings are unsafe (`NODE_ENV` not `production`, `INDIEKIT_ALLOW_DEV_AUTH=1`, weak `SECRET`, missing/invalid `PASSWORD_SECRET`, or empty-password hash).
|
||||
@@ -73,6 +74,7 @@
|
||||
- Startup scripts parse `.env` with the `dotenv` parser (not shell `source`), so values containing spaces are handled safely.
|
||||
- Startup scripts run preflight + patch helpers before boot (`scripts/preflight-production-security.mjs`, `scripts/preflight-mongo-connection.mjs`, `scripts/patch-lightningcss.mjs`, `scripts/patch-endpoint-media-scope.mjs`, `scripts/patch-endpoint-media-sharp-runtime.mjs`, `scripts/patch-frontend-sharp-runtime.mjs`, `scripts/patch-endpoint-files-upload-route.mjs`, `scripts/patch-endpoint-files-upload-locales.mjs`, `scripts/patch-frontend-serviceworker-file.mjs`, `scripts/patch-conversations-collection-guards.mjs`, `scripts/patch-indieauth-devmode-guard.mjs`).
|
||||
- The production security preflight blocks startup on insecure auth/session configuration and catches empty-password bcrypt hashes.
|
||||
- One-time recovery mode is available with `INDIEKIT_ALLOW_PASSWORD_SETUP=1` to bootstrap/reset `PASSWORD_SECRET` when locked out. Remove this flag after setting a valid hash.
|
||||
- The media scope patch fixes a known upstream issue where file uploads can fail if the token scope is `create update delete` without explicit `media`.
|
||||
- The media sharp runtime patch makes image transformation resilient on FreeBSD: if `sharp` cannot load, uploads continue without resize/rotation instead of crashing the server process.
|
||||
- The frontend sharp runtime patch makes icon generation non-fatal on FreeBSD when `sharp` cannot load, preventing startup crashes in asset controller imports.
|
||||
|
||||
@@ -4,6 +4,7 @@ import bcrypt from "bcrypt";
|
||||
|
||||
const strictMode = process.env.REQUIRE_SECURITY !== "0";
|
||||
const nodeEnv = (process.env.NODE_ENV || "").toLowerCase();
|
||||
const allowPasswordSetup = process.env.INDIEKIT_ALLOW_PASSWORD_SETUP === "1";
|
||||
|
||||
function failOrWarn(message) {
|
||||
if (strictMode) {
|
||||
@@ -35,21 +36,29 @@ if (secret.length < 32) {
|
||||
|
||||
const passwordSecret = process.env.PASSWORD_SECRET || "";
|
||||
if (!passwordSecret) {
|
||||
failOrWarn("[preflight] PASSWORD_SECRET is required.");
|
||||
if (allowPasswordSetup) {
|
||||
console.warn(
|
||||
"[preflight] PASSWORD setup recovery mode enabled. Start app, generate a hash at /auth/new-password, then disable INDIEKIT_ALLOW_PASSWORD_SETUP.",
|
||||
);
|
||||
} else {
|
||||
failOrWarn("[preflight] PASSWORD_SECRET is required.");
|
||||
}
|
||||
}
|
||||
|
||||
if (!/^\$2[aby]\$\d{2}\$/.test(passwordSecret)) {
|
||||
if (passwordSecret && !/^\$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.",
|
||||
);
|
||||
if (passwordSecret) {
|
||||
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(
|
||||
|
||||
@@ -18,7 +18,9 @@ if [ -f .env ]; then
|
||||
fi
|
||||
|
||||
: "${SECRET:?SECRET is required}"
|
||||
: "${PASSWORD_SECRET:?PASSWORD_SECRET is required}"
|
||||
if [ "${INDIEKIT_ALLOW_PASSWORD_SETUP:-0}" != "1" ]; then
|
||||
: "${PASSWORD_SECRET:?PASSWORD_SECRET is required}"
|
||||
fi
|
||||
|
||||
# Allow either full Mongo URL or decomposed credentials.
|
||||
if [ -z "${MONGO_URL:-}" ]; then
|
||||
|
||||
Reference in New Issue
Block a user