67 lines
2.5 KiB
JavaScript
67 lines
2.5 KiB
JavaScript
import express from 'express';
|
|
import cors from 'cors';
|
|
import helmet from 'helmet';
|
|
import { config } from './config.js';
|
|
import { migrate, seed } from './db.js';
|
|
import { requestLogger } from './middleware/requestLogger.js';
|
|
import { apiRoutes } from './routes/index.js';
|
|
import { logger } from './services/logger.js';
|
|
import { effectiveTrustedOrigins } from './models/settingsModel.js';
|
|
import { startCatalogWorker } from './services/catalogWorker.js';
|
|
import { startPromotionWorker } from './services/promotionWorker.js';
|
|
|
|
// Fail fast in production rather than ship with well-known default secrets.
|
|
// These defaults are fine for local development but are publicly known, so a
|
|
// production deployment that kept them would have forgeable sessions and
|
|
// recoverable credential ciphertext.
|
|
function assertProductionSecrets() {
|
|
if (!config.isProduction) return;
|
|
const insecure = [];
|
|
if (!process.env.JWT_SECRET || config.jwtSecret === 'dev-only-change-me') insecure.push('JWT_SECRET');
|
|
if (!process.env.CREDENTIAL_STORE_KEY || config.credentialStoreKey === 'dev-only-credential-key') {
|
|
insecure.push('CREDENTIAL_STORE_KEY');
|
|
}
|
|
if (config.defaultAdminPassword === 'change-me-now') insecure.push('DEFAULT_ADMIN_PASSWORD');
|
|
if (insecure.length) {
|
|
logger.error('Refusing to start in production with insecure default secrets', { insecure });
|
|
throw new Error(`Set non-default values for: ${insecure.join(', ')} before starting in production.`);
|
|
}
|
|
}
|
|
|
|
assertProductionSecrets();
|
|
|
|
migrate();
|
|
seed();
|
|
|
|
const app = express();
|
|
|
|
// The API always runs behind the Vite proxy (and typically a reverse proxy in
|
|
// production), so honor X-Forwarded-* to get the real client IP for rate
|
|
// limiting and logging.
|
|
app.set('trust proxy', true);
|
|
|
|
app.use(helmet({ contentSecurityPolicy: false }));
|
|
app.use(cors({
|
|
origin(origin, callback) {
|
|
// Read the effective allow-list per request so admin edits to
|
|
// `trusted_origins` in the Config screen take effect without a restart.
|
|
if (!origin || effectiveTrustedOrigins().includes(origin)) return callback(null, true);
|
|
return callback(new Error(`Origin ${origin} is not allowed by CORS`));
|
|
},
|
|
credentials: true
|
|
}));
|
|
app.use(express.json({ limit: config.jsonLimit }));
|
|
app.use(requestLogger);
|
|
app.use('/api', apiRoutes);
|
|
|
|
app.use((error, req, res, next) => {
|
|
logger.error('unhandled error', { error });
|
|
res.status(500).json({ error: 'Unexpected server error' });
|
|
});
|
|
|
|
app.listen(config.port, () => {
|
|
logger.info(`POSHManager API listening on ${config.port}`);
|
|
startCatalogWorker();
|
|
startPromotionWorker();
|
|
});
|