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'; import { startHostGroupWorker } from './services/hostGroupWorker.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(); startHostGroupWorker(); });