/** * ABE API Server — Express app factory. * Middleware order matters: requestId → helmet → cors → rateLimit → body → routes → notFound → errorHandler */ import express, { Express, Request, Response } from 'express'; import cors from 'cors'; import helmet from 'helmet'; import rateLimit from 'express-rate-limit'; import cookieParser from 'cookie-parser'; import { Kysely } from 'kysely'; import { AppConfig } from '../shared/infrastructure/Config'; import { Logger } from '../shared/infrastructure/Logger'; import { Database } from '../shared/infrastructure/DatabaseConnection'; import { createRequestIdMiddleware } from './middleware/requestId'; import { notFoundMiddleware } from './middleware/notFound'; import { globalErrorHandler } from './middleware/errorHandler'; import { createRouter } from './router'; import { createApiDocsRouter } from './openapi'; import { CrawlingControllerDeps } from '../modules/crawling/infrastructure/http/CrawlingController'; import { FindingsControllerDeps } from '../modules/findings/infrastructure/http/FindingsController'; import { FuzzingControllerDeps } from '../modules/fuzzing/infrastructure/http/FuzzingController'; import { ReportingControllerDeps } from '../modules/reporting/infrastructure/http/ReportingController'; import { IntegrationsDeps } from '../modules/integrations/infrastructure/http/IntegrationsController'; import { AuthControllerDeps } from './router'; import { LicenseService } from '../modules/licensing/application/LicenseService'; import { SchedulingControllerDeps } from '../modules/scheduling/infrastructure/http/SchedulingController'; import { VisualRegressionControllerDeps } from '../modules/visual-regression/infrastructure/http/VisualRegressionController'; export interface ServerDependencies { config: AppConfig; logger: Logger; db: Kysely; crawlingDeps: CrawlingControllerDeps; findingsDeps: FindingsControllerDeps; fuzzingDeps: FuzzingControllerDeps; reportingDeps: ReportingControllerDeps; integrationsDeps: IntegrationsDeps; schedulingDeps: SchedulingControllerDeps; visualRegressionDeps: VisualRegressionControllerDeps; authDeps: AuthControllerDeps; licenseService: LicenseService; } export function createServer(deps: ServerDependencies): Express { const app = express(); // 1. Request ID — must be first so all logs have requestId app.use(createRequestIdMiddleware(deps.logger)); // 2. Security headers app.use( helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], connectSrc: ["'self'", 'ws:', 'wss:'], scriptSrc: ["'self'", "'unsafe-inline'"], }, }, }), ); // 3. CORS app.use(cors({ origin: deps.config.cors.origin, credentials: true })); // 4. Rate limiting app.use( rateLimit({ windowMs: deps.config.api.rateLimitWindowMs, max: deps.config.api.rateLimitMax, standardHeaders: true, legacyHeaders: false, }), ); // 5. Body parsing + cookies app.use(express.json({ limit: '10mb' })); app.use(cookieParser()); // 6. Health endpoints — no auth required app.get('/health/live', (_req: Request, res: Response) => { res.json({ status: 'ok', uptime: process.uptime() }); }); app.get('/health/ready', async (_req: Request, res: Response) => { try { await deps.db.selectFrom('sessions').select('id').limit(1).execute(); res.json({ status: 'ready', db: 'connected' }); } catch (err) { res.status(503).json({ status: 'not_ready', db: 'disconnected', error: String(err) }); } }); // 7. Module routes app.use('/api', createRouter(deps)); // 7b. API documentation (no auth required) app.use('/api-docs', createApiDocsRouter()); // 8. 404 handler app.use(notFoundMiddleware); // 9. Global error handler — always last app.use(globalErrorHandler); return app; }