Files
Autonomous-Bug-Explorer/src/main.ts

232 lines
9.8 KiB
TypeScript

/**
* ABE — composition root.
* Wires all modules together and starts the HTTP + WebSocket server.
*/
import http from 'http';
import { Server as SocketIOServer } from 'socket.io';
import { loadConfig } from './shared/infrastructure/Config';
import { createLogger } from './shared/infrastructure/Logger';
import { createDatabase } from './shared/infrastructure/DatabaseConnection';
import { InProcessEventBus } from './shared/infrastructure/InProcessEventBus';
import { runMigrations } from './db/migrator';
// Crawling module
import { KyselyCrawlSessionRepository } from './modules/crawling/infrastructure/repositories/KyselyCrawlSessionRepository';
import { KyselyStateRepository } from './modules/crawling/infrastructure/repositories/KyselyStateRepository';
import { StartCrawlCommand } from './modules/crawling/application/commands/StartCrawlCommand';
import { StopCrawlCommand } from './modules/crawling/application/commands/StopCrawlCommand';
import { GetSessionQuery } from './modules/crawling/application/queries/GetSessionQuery';
import { ListSessionsQuery } from './modules/crawling/application/queries/ListSessionsQuery';
// Findings module
import { KyselyFindingRepository } from './modules/findings/infrastructure/repositories/KyselyFindingRepository';
import { CreateFindingCommand } from './modules/findings/application/commands/CreateFindingCommand';
import { EnrichFindingCommand } from './modules/findings/application/commands/EnrichFindingCommand';
import { ResolveFindingCommand } from './modules/findings/application/commands/ResolveFindingCommand';
import { GetFindingQuery } from './modules/findings/application/queries/GetFindingQuery';
import { ListFindingsQuery } from './modules/findings/application/queries/ListFindingsQuery';
import { FindingStatsQuery } from './modules/findings/application/queries/FindingStatsQuery';
import { OnAnomalyDetected } from './modules/findings/application/event-handlers/OnAnomalyDetected';
import { NullAIEnricher } from './modules/findings/infrastructure/NullAIEnricher';
// Fuzzing module
import { FuzzingEngineAdapter } from './modules/fuzzing/infrastructure/adapters/FuzzingEngineAdapter';
import { RunFuzzCommand } from './modules/fuzzing/application/commands/RunFuzzCommand';
import { OnActionExecuted } from './modules/fuzzing/application/event-handlers/OnActionExecuted';
import { InMemoryFuzzSessionRepository } from './modules/fuzzing/infrastructure/repositories/InMemoryFuzzSessionRepository';
// Auth module
import { KyselyUserRepository } from './modules/auth/infrastructure/repositories/KyselyUserRepository';
import { KyselyOrganizationRepository } from './modules/auth/infrastructure/repositories/KyselyOrganizationRepository';
import { KyselyApiKeyRepository } from './modules/auth/infrastructure/repositories/KyselyApiKeyRepository';
import { KyselySessionRepository } from './modules/auth/infrastructure/repositories/KyselySessionRepository';
import { RegisterCommand } from './modules/auth/application/commands/RegisterCommand';
import { LoginCommand } from './modules/auth/application/commands/LoginCommand';
import { CreateOrganizationCommand } from './modules/auth/application/commands/CreateOrganizationCommand';
import { InviteMemberCommand } from './modules/auth/application/commands/InviteMemberCommand';
import { CreateApiKeyCommand } from './modules/auth/application/commands/CreateApiKeyCommand';
import { GetUserQuery } from './modules/auth/application/queries/GetUserQuery';
import { ListOrgMembersQuery } from './modules/auth/application/queries/ListOrgMembersQuery';
import { hashPassword, verifyPassword } from './modules/auth/infrastructure/auth/PasswordService';
// Reporting module
import { KyselyReportRepository } from './modules/reporting/infrastructure/repositories/KyselyReportRepository';
import { GenerateReportCommand } from './modules/reporting/application/commands/GenerateReportCommand';
// Job queue
import { SQLiteJobQueue } from './jobs/SQLiteJobQueue';
import { createExplorationJobHandler, EXPLORATION_JOB_TYPE } from './jobs/workers/ExplorationWorker';
import { createReportJobHandler, REPORT_JOB_TYPE } from './jobs/workers/ReportWorker';
// API + Realtime
import { createServer } from './api/server';
import { SocketGateway } from './realtime/SocketGateway';
async function bootstrap(): Promise<void> {
// 1. Config
const config = loadConfig();
// 2. Logger
const logger = createLogger({ level: config.log.level, nodeEnv: config.nodeEnv });
logger.info({ port: config.port, env: config.nodeEnv }, 'Starting ABE...');
// 3. Database + migrations
const db = createDatabase(config.db);
await runMigrations(db);
logger.info('Database migrations applied');
// 4. Event bus
const eventBus = new InProcessEventBus(logger);
// 5. Repositories
const sessionRepo = new KyselyCrawlSessionRepository(db);
const stateRepo = new KyselyStateRepository(db);
const findingRepo = new KyselyFindingRepository(db);
const reportRepo = new KyselyReportRepository(db);
const fuzzRepo = new InMemoryFuzzSessionRepository();
// Suppress unused warning for stateRepo — used by crawling infrastructure
void stateRepo;
// 6. Crawling use cases
const startCrawl = new StartCrawlCommand(sessionRepo, eventBus);
const stopCrawl = new StopCrawlCommand(sessionRepo, eventBus);
const getSession = new GetSessionQuery(sessionRepo);
const listSessions = new ListSessionsQuery(sessionRepo);
// 7. Findings use cases
const createFinding = new CreateFindingCommand(findingRepo, eventBus);
const enricher = new NullAIEnricher();
const enrichFinding = new EnrichFindingCommand(findingRepo, enricher, eventBus);
const resolveFinding = new ResolveFindingCommand(findingRepo, eventBus);
const getFinding = new GetFindingQuery(findingRepo);
const listFindings = new ListFindingsQuery(findingRepo);
const findingStats = new FindingStatsQuery(findingRepo);
// 8. Fuzzing use cases
const fuzzerEngine = new FuzzingEngineAdapter({ intensity: 'low', seed: 42 });
const runFuzz = new RunFuzzCommand(fuzzerEngine, fuzzRepo, eventBus);
// 9. Event handlers — subscribe to EventBus
const onAnomalyDetected = new OnAnomalyDetected(createFinding);
eventBus.subscribe('crawling.anomaly_detected', onAnomalyDetected);
const onActionExecuted = new OnActionExecuted(runFuzz);
eventBus.subscribe('crawling.action_executed', onActionExecuted);
// 10. Auth module
const userRepo = new KyselyUserRepository(db);
const orgRepo = new KyselyOrganizationRepository(db);
const apiKeyRepo = new KyselyApiKeyRepository(db);
const authSessionRepo = new KyselySessionRepository(db);
const registerCommand = new RegisterCommand(userRepo, eventBus, hashPassword);
const loginCommand = new LoginCommand(userRepo, authSessionRepo, eventBus, verifyPassword);
const createOrgCommand = new CreateOrganizationCommand(orgRepo, userRepo, eventBus);
const inviteMemberCommand = new InviteMemberCommand(orgRepo, userRepo, eventBus);
const createApiKeyCommand = new CreateApiKeyCommand(apiKeyRepo, userRepo);
const getUserQuery = new GetUserQuery(userRepo);
const listOrgMembersQuery = new ListOrgMembersQuery(orgRepo, userRepo);
// 11. Reporting use cases
const generateReport = new GenerateReportCommand(reportRepo, eventBus);
// 12. Job queue (created before HTTP server so it can be injected)
const jobQueue = new SQLiteJobQueue(db, logger, config.jobs.pollIntervalMs);
jobQueue.registerHandler(
EXPLORATION_JOB_TYPE,
createExplorationJobHandler({ sessionRepo, eventBus, logger }),
);
jobQueue.registerHandler(REPORT_JOB_TYPE, createReportJobHandler({ logger, reportRepository: reportRepo, findingRepository: findingRepo }));
jobQueue.start();
// 13. HTTP server
const app = createServer({
config,
logger,
db,
crawlingDeps: { startCrawl, stopCrawl, getSession, listSessions },
findingsDeps: { getFinding, listFindings, findingStats, resolveFinding, enrichFinding },
fuzzingDeps: { runFuzz, repository: fuzzRepo },
reportingDeps: { generateReport, reportRepository: reportRepo, jobQueue },
authDeps: {
registerCommand,
loginCommand,
createOrgCommand,
inviteMemberCommand,
createApiKeyCommand,
getUserQuery,
listOrgMembersQuery,
sessionRepository: authSessionRepo,
apiKeyRepository: apiKeyRepo,
userRepository: userRepo,
},
});
const httpServer = http.createServer(app);
// 12. Socket.io + gateway
const io = new SocketIOServer(httpServer, {
cors: { origin: config.cors.origin, credentials: true },
});
const gateway = new SocketGateway(io, eventBus, logger);
gateway.start();
// 13. Start listening
await new Promise<void>((resolve) => {
httpServer.listen(config.port, config.host, resolve);
});
logger.info({ port: config.port, host: config.host }, 'ABE server ready');
// 14. Graceful shutdown
let shuttingDown = false;
async function shutdown(signal: string): Promise<void> {
if (shuttingDown) return;
shuttingDown = true;
logger.info({ signal }, 'Shutting down...');
// Stop accepting new connections
httpServer.close();
// Close socket.io
io.close();
// Stop job queue and wait for active jobs
jobQueue.pause();
await jobQueue.waitForActive(30_000);
// 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: string): void {
void shutdown(signal).catch(() => {
process.exit(1);
});
setTimeout(() => {
logger.error('Forced shutdown after 30s');
process.exit(1);
}, 30_000).unref();
}
process.on('SIGTERM', () => forceExit('SIGTERM'));
process.on('SIGINT', () => forceExit('SIGINT'));
}
bootstrap().catch((err: unknown) => {
console.error('Fatal: failed to start ABE', err);
process.exit(1);
});