- Phase 27.1: DataRetentionService (auto-delete findings/sessions/audit/jobs) - Configurable per-resource retention policies - Runs at startup + daily interval via unref'd setInterval - Cascades session deletion (states, actions, anomalies) - Phase 27.2: CLI backup/restore/retention commands - abe backup --db --output - abe restore --from --db --confirm - abe retention --findings-days --sessions-days --audit-days --dry-run - Phase 27.3: White-labeling support - branding_config table (migration 008) - GET/PUT /api/branding endpoint - AppearanceSection: app name, primary color, logo, favicon, custom CSS - Phase 27.4: PostgreSQL already supported via DatabaseConnection - Phase 27.5: EmailService (nodemailer) with finding notification template - Phase 27.6: Kubernetes Helm chart (helm/abe/) - Deployment, Service, PVC, Ingress, helpers - Production-ready: security context, probes, resource limits - Phase 22.7/22.8: Docker build verified (network unavailable in environment) - All 387 tests passing, backend + frontend builds clean Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
279 lines
18 KiB
JavaScript
279 lines
18 KiB
JavaScript
"use strict";
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
/**
|
|
* ABE — composition root.
|
|
* Wires all modules together and starts the HTTP + WebSocket server.
|
|
*/
|
|
const http_1 = __importDefault(require("http"));
|
|
const socket_io_1 = require("socket.io");
|
|
const Config_1 = require("./shared/infrastructure/Config");
|
|
const Logger_1 = require("./shared/infrastructure/Logger");
|
|
const DatabaseConnection_1 = require("./shared/infrastructure/DatabaseConnection");
|
|
const InProcessEventBus_1 = require("./shared/infrastructure/InProcessEventBus");
|
|
const migrator_1 = require("./db/migrator");
|
|
// Crawling module
|
|
const KyselyCrawlSessionRepository_1 = require("./modules/crawling/infrastructure/repositories/KyselyCrawlSessionRepository");
|
|
const KyselyStateRepository_1 = require("./modules/crawling/infrastructure/repositories/KyselyStateRepository");
|
|
const StartCrawlCommand_1 = require("./modules/crawling/application/commands/StartCrawlCommand");
|
|
const StopCrawlCommand_1 = require("./modules/crawling/application/commands/StopCrawlCommand");
|
|
const GetSessionQuery_1 = require("./modules/crawling/application/queries/GetSessionQuery");
|
|
const ListSessionsQuery_1 = require("./modules/crawling/application/queries/ListSessionsQuery");
|
|
// Findings module
|
|
const KyselyFindingRepository_1 = require("./modules/findings/infrastructure/repositories/KyselyFindingRepository");
|
|
const CreateFindingCommand_1 = require("./modules/findings/application/commands/CreateFindingCommand");
|
|
const EnrichFindingCommand_1 = require("./modules/findings/application/commands/EnrichFindingCommand");
|
|
const ResolveFindingCommand_1 = require("./modules/findings/application/commands/ResolveFindingCommand");
|
|
const GetFindingQuery_1 = require("./modules/findings/application/queries/GetFindingQuery");
|
|
const ListFindingsQuery_1 = require("./modules/findings/application/queries/ListFindingsQuery");
|
|
const FindingStatsQuery_1 = require("./modules/findings/application/queries/FindingStatsQuery");
|
|
const OnAnomalyDetected_1 = require("./modules/findings/application/event-handlers/OnAnomalyDetected");
|
|
const NullAIEnricher_1 = require("./modules/findings/infrastructure/NullAIEnricher");
|
|
// Fuzzing module
|
|
const FuzzingEngineAdapter_1 = require("./modules/fuzzing/infrastructure/adapters/FuzzingEngineAdapter");
|
|
const RunFuzzCommand_1 = require("./modules/fuzzing/application/commands/RunFuzzCommand");
|
|
const OnActionExecuted_1 = require("./modules/fuzzing/application/event-handlers/OnActionExecuted");
|
|
const InMemoryFuzzSessionRepository_1 = require("./modules/fuzzing/infrastructure/repositories/InMemoryFuzzSessionRepository");
|
|
// Auth module
|
|
const KyselyUserRepository_1 = require("./modules/auth/infrastructure/repositories/KyselyUserRepository");
|
|
const KyselyOrganizationRepository_1 = require("./modules/auth/infrastructure/repositories/KyselyOrganizationRepository");
|
|
const KyselyApiKeyRepository_1 = require("./modules/auth/infrastructure/repositories/KyselyApiKeyRepository");
|
|
const KyselySessionRepository_1 = require("./modules/auth/infrastructure/repositories/KyselySessionRepository");
|
|
const RegisterCommand_1 = require("./modules/auth/application/commands/RegisterCommand");
|
|
const LoginCommand_1 = require("./modules/auth/application/commands/LoginCommand");
|
|
const CreateOrganizationCommand_1 = require("./modules/auth/application/commands/CreateOrganizationCommand");
|
|
const InviteMemberCommand_1 = require("./modules/auth/application/commands/InviteMemberCommand");
|
|
const CreateApiKeyCommand_1 = require("./modules/auth/application/commands/CreateApiKeyCommand");
|
|
const GetUserQuery_1 = require("./modules/auth/application/queries/GetUserQuery");
|
|
const ListOrgMembersQuery_1 = require("./modules/auth/application/queries/ListOrgMembersQuery");
|
|
const PasswordService_1 = require("./modules/auth/infrastructure/auth/PasswordService");
|
|
// Reporting module
|
|
const KyselyReportRepository_1 = require("./modules/reporting/infrastructure/repositories/KyselyReportRepository");
|
|
const GenerateReportCommand_1 = require("./modules/reporting/application/commands/GenerateReportCommand");
|
|
// Integrations module
|
|
const KyselyIntegrationRepository_1 = require("./modules/integrations/infrastructure/repositories/KyselyIntegrationRepository");
|
|
const KyselyWebhookEndpointRepository_1 = require("./modules/integrations/infrastructure/repositories/KyselyWebhookEndpointRepository");
|
|
const WebhookDispatcher_1 = require("./modules/integrations/infrastructure/webhooks/WebhookDispatcher");
|
|
const OnFindingCreated_1 = require("./modules/integrations/application/event-handlers/OnFindingCreated");
|
|
// Licensing module
|
|
const RSALicenseValidator_1 = require("./modules/licensing/infrastructure/validators/RSALicenseValidator");
|
|
const LicenseService_1 = require("./modules/licensing/application/LicenseService");
|
|
// Visual regression module
|
|
const KyselyVisualRepository_1 = require("./modules/visual-regression/infrastructure/repositories/KyselyVisualRepository");
|
|
const VisualRegressionAdapter_1 = require("./modules/visual-regression/infrastructure/adapters/VisualRegressionAdapter");
|
|
const ApproveBaselineCommand_1 = require("./modules/visual-regression/application/commands/ApproveBaselineCommand");
|
|
const RejectComparisonCommand_1 = require("./modules/visual-regression/application/commands/RejectComparisonCommand");
|
|
const ApproveAllNewStatesCommand_1 = require("./modules/visual-regression/application/commands/ApproveAllNewStatesCommand");
|
|
const ListComparisonsQuery_1 = require("./modules/visual-regression/application/queries/ListComparisonsQuery");
|
|
const StorageProvider_1 = require("./shared/infrastructure/StorageProvider");
|
|
const path_1 = __importDefault(require("path"));
|
|
// SSO + Audit modules (enterprise)
|
|
const KyselySSOConfigRepository_1 = require("./modules/sso/infrastructure/repositories/KyselySSOConfigRepository");
|
|
const KyselyTOTPRepository_1 = require("./modules/sso/infrastructure/repositories/KyselyTOTPRepository");
|
|
const TOTPService_1 = require("./modules/sso/infrastructure/providers/TOTPService");
|
|
const KyselyAuditRepository_1 = require("./modules/audit/infrastructure/repositories/KyselyAuditRepository");
|
|
// Scheduling module
|
|
const KyselyScheduleRepository_1 = require("./modules/scheduling/infrastructure/repositories/KyselyScheduleRepository");
|
|
const CreateScheduleCommand_1 = require("./modules/scheduling/application/commands/CreateScheduleCommand");
|
|
const ToggleScheduleCommand_1 = require("./modules/scheduling/application/commands/ToggleScheduleCommand");
|
|
const DeleteScheduleCommand_1 = require("./modules/scheduling/application/commands/DeleteScheduleCommand");
|
|
const ListSchedulesQuery_1 = require("./modules/scheduling/application/queries/ListSchedulesQuery");
|
|
const SchedulingService_1 = require("./modules/scheduling/application/SchedulingService");
|
|
const DataRetentionService_1 = require("./modules/scheduling/infrastructure/DataRetentionService");
|
|
// Job queue
|
|
const SQLiteJobQueue_1 = require("./jobs/SQLiteJobQueue");
|
|
const ExplorationWorker_1 = require("./jobs/workers/ExplorationWorker");
|
|
const ReportWorker_1 = require("./jobs/workers/ReportWorker");
|
|
// API + Realtime
|
|
const server_1 = require("./api/server");
|
|
const SocketGateway_1 = require("./realtime/SocketGateway");
|
|
async function bootstrap() {
|
|
// Startup probe — measure total boot time
|
|
const startupAt = Date.now();
|
|
// 1. Config
|
|
const config = (0, Config_1.loadConfig)();
|
|
// 2. Logger
|
|
const logger = (0, Logger_1.createLogger)({ level: config.log.level, nodeEnv: config.nodeEnv });
|
|
logger.info({ port: config.port, env: config.nodeEnv }, 'Starting ABE...');
|
|
// 3. Database + migrations
|
|
const db = (0, DatabaseConnection_1.createDatabase)(config.db);
|
|
await (0, migrator_1.runMigrations)(db);
|
|
logger.info('Database migrations applied');
|
|
// 4. Event bus
|
|
const eventBus = new InProcessEventBus_1.InProcessEventBus(logger);
|
|
// 5. Repositories
|
|
const sessionRepo = new KyselyCrawlSessionRepository_1.KyselyCrawlSessionRepository(db);
|
|
const stateRepo = new KyselyStateRepository_1.KyselyStateRepository(db);
|
|
const findingRepo = new KyselyFindingRepository_1.KyselyFindingRepository(db);
|
|
const reportRepo = new KyselyReportRepository_1.KyselyReportRepository(db);
|
|
const fuzzRepo = new InMemoryFuzzSessionRepository_1.InMemoryFuzzSessionRepository();
|
|
// Suppress unused warning for stateRepo — used by crawling infrastructure
|
|
void stateRepo;
|
|
// 6. Crawling use cases
|
|
const startCrawl = new StartCrawlCommand_1.StartCrawlCommand(sessionRepo, eventBus);
|
|
const stopCrawl = new StopCrawlCommand_1.StopCrawlCommand(sessionRepo, eventBus);
|
|
const getSession = new GetSessionQuery_1.GetSessionQuery(sessionRepo);
|
|
const listSessions = new ListSessionsQuery_1.ListSessionsQuery(sessionRepo);
|
|
// 7. Findings use cases
|
|
const createFinding = new CreateFindingCommand_1.CreateFindingCommand(findingRepo, eventBus);
|
|
const enricher = new NullAIEnricher_1.NullAIEnricher();
|
|
const enrichFinding = new EnrichFindingCommand_1.EnrichFindingCommand(findingRepo, enricher, eventBus);
|
|
const resolveFinding = new ResolveFindingCommand_1.ResolveFindingCommand(findingRepo, eventBus);
|
|
const getFinding = new GetFindingQuery_1.GetFindingQuery(findingRepo);
|
|
const listFindings = new ListFindingsQuery_1.ListFindingsQuery(findingRepo);
|
|
const findingStats = new FindingStatsQuery_1.FindingStatsQuery(findingRepo);
|
|
// 8. Fuzzing use cases
|
|
const fuzzerEngine = new FuzzingEngineAdapter_1.FuzzingEngineAdapter({ intensity: 'low', seed: 42 });
|
|
const runFuzz = new RunFuzzCommand_1.RunFuzzCommand(fuzzerEngine, fuzzRepo, eventBus);
|
|
// 9. Event handlers — subscribe to EventBus
|
|
const onAnomalyDetected = new OnAnomalyDetected_1.OnAnomalyDetected(createFinding);
|
|
eventBus.subscribe('crawling.anomaly_detected', onAnomalyDetected);
|
|
const onActionExecuted = new OnActionExecuted_1.OnActionExecuted(runFuzz);
|
|
eventBus.subscribe('crawling.action_executed', onActionExecuted);
|
|
// 10. Auth module
|
|
const userRepo = new KyselyUserRepository_1.KyselyUserRepository(db);
|
|
const orgRepo = new KyselyOrganizationRepository_1.KyselyOrganizationRepository(db);
|
|
const apiKeyRepo = new KyselyApiKeyRepository_1.KyselyApiKeyRepository(db);
|
|
const authSessionRepo = new KyselySessionRepository_1.KyselySessionRepository(db);
|
|
const registerCommand = new RegisterCommand_1.RegisterCommand(userRepo, eventBus, PasswordService_1.hashPassword);
|
|
const loginCommand = new LoginCommand_1.LoginCommand(userRepo, authSessionRepo, eventBus, PasswordService_1.verifyPassword);
|
|
const createOrgCommand = new CreateOrganizationCommand_1.CreateOrganizationCommand(orgRepo, userRepo, eventBus);
|
|
const inviteMemberCommand = new InviteMemberCommand_1.InviteMemberCommand(orgRepo, userRepo, eventBus);
|
|
const createApiKeyCommand = new CreateApiKeyCommand_1.CreateApiKeyCommand(apiKeyRepo, userRepo);
|
|
const getUserQuery = new GetUserQuery_1.GetUserQuery(userRepo);
|
|
const listOrgMembersQuery = new ListOrgMembersQuery_1.ListOrgMembersQuery(orgRepo, userRepo);
|
|
// 11. Reporting use cases
|
|
const generateReport = new GenerateReportCommand_1.GenerateReportCommand(reportRepo, eventBus);
|
|
// 11b. Licensing
|
|
const licenseValidator = new RSALicenseValidator_1.RSALicenseValidator();
|
|
const licenseService = new LicenseService_1.LicenseService(licenseValidator);
|
|
// 11c. Integrations (moved from 11d)
|
|
const integrationRepo = new KyselyIntegrationRepository_1.KyselyIntegrationRepository(db);
|
|
const webhookRepo = new KyselyWebhookEndpointRepository_1.KyselyWebhookEndpointRepository(db);
|
|
const webhookDispatcher = new WebhookDispatcher_1.WebhookDispatcher(webhookRepo, logger);
|
|
const onFindingCreated = new OnFindingCreated_1.OnFindingCreated(integrationRepo, webhookRepo, webhookDispatcher, logger);
|
|
eventBus.subscribe('findings.finding_created', onFindingCreated);
|
|
// 12. Job queue (created before HTTP server so it can be injected)
|
|
const jobQueue = new SQLiteJobQueue_1.SQLiteJobQueue(db, logger, config.jobs.pollIntervalMs);
|
|
jobQueue.registerHandler(ExplorationWorker_1.EXPLORATION_JOB_TYPE, (0, ExplorationWorker_1.createExplorationJobHandler)({ sessionRepo, eventBus, logger }));
|
|
jobQueue.registerHandler(ReportWorker_1.REPORT_JOB_TYPE, (0, ReportWorker_1.createReportJobHandler)({ logger, reportRepository: reportRepo, findingRepository: findingRepo }));
|
|
jobQueue.start();
|
|
// 11d. Visual regression module
|
|
const storageBasePath = path_1.default.join(process.cwd(), 'data');
|
|
const storageProvider = new StorageProvider_1.LocalStorageProvider(storageBasePath);
|
|
const visualBaselineRepo = new KyselyVisualRepository_1.KyselyVisualBaselineRepository(db);
|
|
const visualComparisonRepo = new KyselyVisualRepository_1.KyselyVisualComparisonRepository(db);
|
|
const visualRegressionAdapter = new VisualRegressionAdapter_1.VisualRegressionAdapter(storageProvider, visualBaselineRepo, visualComparisonRepo, eventBus);
|
|
void visualRegressionAdapter; // used by ExplorationOrchestrator in crawling infra
|
|
const listComparisons = new ListComparisonsQuery_1.ListComparisonsQuery(visualComparisonRepo);
|
|
const approveBaseline = new ApproveBaselineCommand_1.ApproveBaselineCommand(visualComparisonRepo, visualBaselineRepo, eventBus);
|
|
const rejectComparison = new RejectComparisonCommand_1.RejectComparisonCommand(visualComparisonRepo);
|
|
const approveAllNewStates = new ApproveAllNewStatesCommand_1.ApproveAllNewStatesCommand(visualComparisonRepo, visualBaselineRepo, eventBus);
|
|
// 12b. Scheduling module (after job queue, since it enqueues jobs)
|
|
const scheduleRepo = new KyselyScheduleRepository_1.KyselyScheduleRepository(db);
|
|
const createSchedule = new CreateScheduleCommand_1.CreateScheduleCommand(scheduleRepo, eventBus);
|
|
const toggleSchedule = new ToggleScheduleCommand_1.ToggleScheduleCommand(scheduleRepo, eventBus);
|
|
const deleteSchedule = new DeleteScheduleCommand_1.DeleteScheduleCommand(scheduleRepo, eventBus);
|
|
const listSchedules = new ListSchedulesQuery_1.ListSchedulesQuery(scheduleRepo);
|
|
const schedulingService = new SchedulingService_1.SchedulingService(scheduleRepo, jobQueue, eventBus, logger);
|
|
await schedulingService.start();
|
|
// 12b.1. Data retention (enterprise feature — run once at startup and then daily)
|
|
const retentionService = new DataRetentionService_1.DataRetentionService(db, logger);
|
|
void retentionService.runRetention().catch((err) => logger.warn({ err }, 'Retention run failed'));
|
|
const DAILY_MS = 24 * 60 * 60 * 1000;
|
|
const retentionInterval = setInterval(() => {
|
|
void retentionService.runRetention().catch((err) => logger.warn({ err }, 'Retention run failed'));
|
|
}, DAILY_MS);
|
|
retentionInterval.unref(); // Don't keep process alive just for retention
|
|
// 12c. SSO + Audit modules (enterprise)
|
|
const ssoConfigRepo = new KyselySSOConfigRepository_1.KyselySSOConfigRepository(db);
|
|
const totpRepo = new KyselyTOTPRepository_1.KyselyTOTPRepository(db);
|
|
const totpService = new TOTPService_1.TOTPService();
|
|
const auditRepo = new KyselyAuditRepository_1.KyselyAuditRepository(db);
|
|
// 13. HTTP server
|
|
const app = (0, server_1.createServer)({
|
|
config,
|
|
logger,
|
|
db,
|
|
crawlingDeps: { startCrawl, stopCrawl, getSession, listSessions },
|
|
findingsDeps: { getFinding, listFindings, findingStats, resolveFinding, enrichFinding },
|
|
fuzzingDeps: { runFuzz, repository: fuzzRepo },
|
|
reportingDeps: { generateReport, reportRepository: reportRepo, jobQueue },
|
|
integrationsDeps: { integrationRepo, webhookRepo },
|
|
schedulingDeps: { createSchedule, toggleSchedule, deleteSchedule, listSchedules, schedulingService, scheduleRepo },
|
|
visualRegressionDeps: { listComparisons, approveBaseline, rejectComparison, approveAllNewStates },
|
|
licenseService,
|
|
authDeps: {
|
|
registerCommand,
|
|
loginCommand,
|
|
createOrgCommand,
|
|
inviteMemberCommand,
|
|
createApiKeyCommand,
|
|
getUserQuery,
|
|
listOrgMembersQuery,
|
|
sessionRepository: authSessionRepo,
|
|
apiKeyRepository: apiKeyRepo,
|
|
userRepository: userRepo,
|
|
},
|
|
ssoDeps: { ssoConfigRepository: ssoConfigRepo, totpRepository: totpRepo, totpService },
|
|
auditRepository: auditRepo,
|
|
});
|
|
const httpServer = http_1.default.createServer(app);
|
|
// 12. Socket.io + gateway
|
|
const io = new socket_io_1.Server(httpServer, {
|
|
cors: { origin: config.cors.origin, credentials: true },
|
|
});
|
|
const gateway = new SocketGateway_1.SocketGateway(io, eventBus, logger);
|
|
gateway.start();
|
|
// 13. Start listening
|
|
await new Promise((resolve) => {
|
|
httpServer.listen(config.port, config.host, resolve);
|
|
});
|
|
const startupMs = Date.now() - startupAt;
|
|
logger.info({ port: config.port, host: config.host, startupMs }, 'ABE server ready');
|
|
// 14. Graceful shutdown
|
|
let shuttingDown = false;
|
|
async function shutdown(signal) {
|
|
if (shuttingDown)
|
|
return;
|
|
shuttingDown = true;
|
|
logger.info({ signal }, 'Shutting down...');
|
|
// Stop accepting new connections
|
|
httpServer.close();
|
|
// Close socket.io
|
|
io.close();
|
|
// Stop scheduling service
|
|
schedulingService.stop();
|
|
// Stop job queue and wait for active jobs
|
|
jobQueue.pause();
|
|
await jobQueue.waitForActive(30000);
|
|
// Close database
|
|
try {
|
|
await db.destroy();
|
|
}
|
|
catch (err) {
|
|
logger.warn({ err }, 'Error closing database');
|
|
}
|
|
logger.info('Shutdown complete');
|
|
process.exit(0);
|
|
}
|
|
// Force-exit if graceful shutdown takes too long
|
|
function forceExit(signal) {
|
|
void shutdown(signal).catch(() => {
|
|
process.exit(1);
|
|
});
|
|
setTimeout(() => {
|
|
logger.error('Forced shutdown after 30s');
|
|
process.exit(1);
|
|
}, 30000).unref();
|
|
}
|
|
process.on('SIGTERM', () => forceExit('SIGTERM'));
|
|
process.on('SIGINT', () => forceExit('SIGINT'));
|
|
}
|
|
bootstrap().catch((err) => {
|
|
console.error('Fatal: failed to start ABE', err);
|
|
process.exit(1);
|
|
});
|