fix(backend): harden runtime config and verification access
This commit is contained in:
@@ -6,10 +6,12 @@ import { errorHandler, notFoundHandler } from './middleware/error-handler.js';
|
||||
import { healthRouter } from './routes/health.js';
|
||||
import { createCommandsRouter } from './routes/commands.js';
|
||||
import { createMobileCommandsRouter } from './routes/mobile.js';
|
||||
import { assertSafeRuntimeConfig } from './lib/runtime-safety.js';
|
||||
|
||||
const logger = pino({ level: process.env.LOG_LEVEL || 'info' });
|
||||
|
||||
export function createApp(options = {}) {
|
||||
assertSafeRuntimeConfig();
|
||||
const app = express();
|
||||
|
||||
app.use(requestContext);
|
||||
|
||||
44
backend/command-api/src/lib/runtime-safety.js
Normal file
44
backend/command-api/src/lib/runtime-safety.js
Normal file
@@ -0,0 +1,44 @@
|
||||
function runtimeEnvName() {
|
||||
return `${process.env.APP_ENV || process.env.NODE_ENV || ''}`.trim().toLowerCase();
|
||||
}
|
||||
|
||||
function isProtectedEnv() {
|
||||
return ['staging', 'prod', 'production'].includes(runtimeEnvName());
|
||||
}
|
||||
|
||||
export function assertSafeRuntimeConfig() {
|
||||
if (!isProtectedEnv()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const errors = [];
|
||||
|
||||
if (process.env.AUTH_BYPASS === 'true') {
|
||||
errors.push('AUTH_BYPASS must be disabled');
|
||||
}
|
||||
|
||||
if (`${process.env.IDEMPOTENCY_STORE || ''}`.trim().toLowerCase() === 'memory') {
|
||||
errors.push('IDEMPOTENCY_STORE must not be memory');
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw new Error(`Unsafe command-api runtime config for ${runtimeEnvName()}: ${errors.join('; ')}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function assertSafeWorkerRuntimeConfig() {
|
||||
if (!isProtectedEnv()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const errors = [];
|
||||
const deliveryMode = `${process.env.PUSH_DELIVERY_MODE || 'live'}`.trim().toLowerCase();
|
||||
|
||||
if (deliveryMode !== 'live') {
|
||||
errors.push('PUSH_DELIVERY_MODE must be live');
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw new Error(`Unsafe notification-worker runtime config for ${runtimeEnvName()}: ${errors.join('; ')}`);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
import express from 'express';
|
||||
import pino from 'pino';
|
||||
import pinoHttp from 'pino-http';
|
||||
import { assertSafeWorkerRuntimeConfig } from './lib/runtime-safety.js';
|
||||
|
||||
const logger = pino({ level: process.env.LOG_LEVEL || 'info' });
|
||||
|
||||
export function createWorkerApp({ dispatch = async () => ({}) } = {}) {
|
||||
assertSafeWorkerRuntimeConfig();
|
||||
const app = express();
|
||||
|
||||
app.use(
|
||||
|
||||
@@ -63,6 +63,16 @@ test('GET /readyz reports database not configured when no database env is presen
|
||||
assert.equal(res.body.status, 'DATABASE_NOT_CONFIGURED');
|
||||
});
|
||||
|
||||
test('createApp fails fast in protected env when auth bypass is enabled', async () => {
|
||||
process.env.APP_ENV = 'staging';
|
||||
process.env.AUTH_BYPASS = 'true';
|
||||
|
||||
assert.throws(() => createApp(), /AUTH_BYPASS must be disabled/);
|
||||
|
||||
delete process.env.APP_ENV;
|
||||
process.env.AUTH_BYPASS = 'true';
|
||||
});
|
||||
|
||||
test('command route requires idempotency key', async () => {
|
||||
const app = createApp();
|
||||
const res = await request(app)
|
||||
|
||||
@@ -12,6 +12,16 @@ test('GET /readyz returns healthy response', async () => {
|
||||
assert.equal(res.body.service, 'notification-worker-v2');
|
||||
});
|
||||
|
||||
test('createWorkerApp fails fast in protected env when push delivery is not live', async () => {
|
||||
process.env.APP_ENV = 'staging';
|
||||
process.env.PUSH_DELIVERY_MODE = 'log-only';
|
||||
|
||||
assert.throws(() => createWorkerApp(), /PUSH_DELIVERY_MODE must be live/);
|
||||
|
||||
delete process.env.APP_ENV;
|
||||
delete process.env.PUSH_DELIVERY_MODE;
|
||||
});
|
||||
|
||||
test('POST /tasks/dispatch-notifications returns dispatch summary', async () => {
|
||||
const app = createWorkerApp({
|
||||
dispatch: async () => ({
|
||||
|
||||
Reference in New Issue
Block a user