Files
Qris-Soundbox/scripts/check-production-env.mjs

142 lines
4.2 KiB
JavaScript

import "dotenv/config";
import path from "node:path";
const required = [
"ADMIN_SESSION_SECRET",
"MERCHANT_SESSION_SECRET",
"INTEGRATION_WEBHOOK_SECRET",
"DATABASE_URL",
"MQTT_BROKER_URL",
"MQTT_USERNAME",
"MQTT_PASSWORD",
"MQTT_CLIENT_ID",
"EXPORT_STORAGE_DIR"
];
const insecureDefaults = new Map([
["ADMIN_TOKEN", "admin-dev-token"],
["MERCHANT_TOKEN", "merchant-dev-token"],
["DEVICE_TOKEN", "device-dev-token"],
["MERCHANT_PORTAL_PASSWORD", "merchant"],
["INTEGRATION_WEBHOOK_SECRET", "dev-callback-secret"],
["MQTT_PASSWORD", "change-me"],
["ADMIN_SESSION_SECRET", "change-me-long-random-admin-session-secret"],
["MERCHANT_SESSION_SECRET", "change-me-long-random-merchant-session-secret"]
]);
const warnings = [];
const errors = [];
for (const name of required) {
if (!process.env[name]) {
errors.push(`${name} is required`);
}
}
for (const [name, value] of insecureDefaults.entries()) {
if (name === "ADMIN_TOKEN" && process.env.ADMIN_AUTH_ALLOW_LEGACY_TOKEN === "false") {
continue;
}
if (name === "DEVICE_TOKEN" && process.env.DEVICE_AUTH_ALLOW_LEGACY_TOKEN === "false") {
continue;
}
if (name === "MERCHANT_TOKEN" && process.env.MERCHANT_AUTH_ALLOW_LEGACY_TOKEN === "false") {
continue;
}
if (name === "MERCHANT_PORTAL_PASSWORD" && process.env.MERCHANT_DEV_LOGIN_ENABLED === "false") {
continue;
}
if (process.env[name] === value) {
errors.push(`${name} still uses the development default`);
}
}
function requireFalse(name) {
if (process.env[name] !== "false") {
errors.push(`${name} must be false in production`);
}
}
function requireTrue(name, message) {
if (process.env[name] !== "true") {
errors.push(message || `${name} must be true in production`);
}
}
requireFalse("ADMIN_AUTH_ALLOW_LEGACY_TOKEN");
requireFalse("DEVICE_AUTH_ALLOW_LEGACY_TOKEN");
requireFalse("MERCHANT_AUTH_ALLOW_LEGACY_TOKEN");
if (process.env.ADMIN_DEV_LOGIN_ENABLED !== "false") {
errors.push("ADMIN_DEV_LOGIN_ENABLED must be false in production");
}
if (process.env.MERCHANT_DEV_LOGIN_ENABLED !== "false") {
errors.push("MERCHANT_DEV_LOGIN_ENABLED must be false in production");
}
if (process.env.MQTT_PUBLISH_MODE !== "broker") {
errors.push("MQTT_PUBLISH_MODE must be broker in production");
}
if (process.env.MQTT_SUBSCRIBE_ENABLED !== "true") {
warnings.push("MQTT_SUBSCRIBE_ENABLED should be true when device uplink observability is required");
}
requireTrue("SETTLEMENT_ADJUSTMENT_REQUIRE_APPROVAL", "SETTLEMENT_ADJUSTMENT_REQUIRE_APPROVAL must be true for production finance control");
if (process.env.DATABASE_URL && !/^postgres(ql)?:\/\//.test(process.env.DATABASE_URL)) {
errors.push("DATABASE_URL must be a postgres connection string");
}
if (process.env.MQTT_BROKER_URL && !/^mqtts:\/\//.test(process.env.MQTT_BROKER_URL)) {
warnings.push("MQTT_BROKER_URL should use mqtts:// for production");
}
for (const name of ["ADMIN_SESSION_SECRET", "MERCHANT_SESSION_SECRET", "INTEGRATION_WEBHOOK_SECRET", "MQTT_PASSWORD"]) {
if ((process.env[name] || "").length < 24) {
errors.push(`${name} must be at least 24 characters`);
}
}
if (process.env.LOG_FORMAT !== "json") {
warnings.push("LOG_FORMAT should be json in production");
}
if (process.env.EXPORT_WORKER_ENABLED !== "true") {
errors.push("EXPORT_WORKER_ENABLED must be true in production");
}
if (Number(process.env.EXPORT_RETENTION_DAYS || 0) <= 0) {
errors.push("EXPORT_RETENTION_DAYS must be a positive number");
}
if (process.env.EXPORT_STORAGE_DIR && !path.isAbsolute(process.env.EXPORT_STORAGE_DIR)) {
errors.push("EXPORT_STORAGE_DIR must be an absolute path in production");
}
if (process.env.RATE_LIMIT_ENABLED !== "true") {
errors.push("RATE_LIMIT_ENABLED must be true in production");
}
if (process.env.TRUST_PROXY !== "true") {
warnings.push("TRUST_PROXY should be true when running behind a reverse proxy/load balancer");
}
if (!process.env.JSON_BODY_LIMIT) {
errors.push("JSON_BODY_LIMIT is required");
}
for (const warning of warnings) {
console.warn(`WARN ${warning}`);
}
if (errors.length) {
for (const error of errors) {
console.error(`ERROR ${error}`);
}
process.exit(1);
}
console.log("Production environment preflight passed");