- Replaced static hosts.json and staticConfig.ts with SQLite database (Prisma) - Implemented JWT authentication and Login UI - Added dynamic API routes for hosts, topology, and settings - Updated UI components to fetch and manage state dynamically - Added Settings interface for managing hosts and topology nodes
120 lines
3.5 KiB
TypeScript
120 lines
3.5 KiB
TypeScript
import express from 'express';
|
|
import cors from 'cors';
|
|
import helmet from 'helmet';
|
|
import rateLimit from 'express-rate-limit';
|
|
import { createServer } from 'http';
|
|
import { Server, Socket } from 'socket.io';
|
|
import discoverRouter from './routes/discover';
|
|
import configRouter from './routes/config';
|
|
import statsRouter from './routes/stats';
|
|
import filesRouter from './routes/files';
|
|
import terminalRouter from './routes/terminal';
|
|
import authRouter from './routes/auth';
|
|
import topologyRouter from './routes/topology';
|
|
import hostsRouter from './routes/hosts';
|
|
import { requireAuth } from './middleware/auth';
|
|
import { getHostConfigs } from './config';
|
|
import { requestLogger, logger } from './middleware/requestLogger';
|
|
import { errorHandler } from './middleware/errorHandler';
|
|
|
|
const app = express();
|
|
const httpServer = createServer(app);
|
|
const PORT = 3001;
|
|
|
|
const allowedOrigins = ['http://localhost:3000', 'http://localhost:4173'];
|
|
const corsOrigin = process.env.CORS_ORIGIN ? [process.env.CORS_ORIGIN, ...allowedOrigins] : allowedOrigins;
|
|
|
|
// --- Socket.IO setup (websocket-engineer skill) ---
|
|
const io = new Server(httpServer, {
|
|
cors: {
|
|
origin: corsOrigin,
|
|
credentials: true,
|
|
},
|
|
pingInterval: 25000,
|
|
pingTimeout: 10000,
|
|
});
|
|
|
|
io.on('connection', (socket: Socket) => {
|
|
logger.info({ socketId: socket.id }, 'Client connected via WebSocket');
|
|
|
|
socket.on('disconnect', (reason: string) => {
|
|
logger.info({ socketId: socket.id, reason }, 'Client disconnected');
|
|
});
|
|
});
|
|
|
|
// Export io so routes can emit events
|
|
export { io };
|
|
|
|
// --- Security middleware (api-security-hardening skill) ---
|
|
app.use(helmet({
|
|
contentSecurityPolicy: false, // Disable CSP for dev — configure per-environment in production
|
|
}));
|
|
|
|
// CORS — restrict to configured origins
|
|
app.use(cors({
|
|
origin: corsOrigin,
|
|
credentials: true,
|
|
}));
|
|
|
|
// Rate limiting — general API
|
|
app.use('/api/', rateLimit({
|
|
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
max: 200,
|
|
message: { status: 'error', message: 'Too many requests, please try again later' },
|
|
standardHeaders: true,
|
|
legacyHeaders: false,
|
|
}));
|
|
|
|
// Stricter rate limiting for discovery (expensive operation)
|
|
app.use('/api/discover', rateLimit({
|
|
windowMs: 1 * 60 * 1000, // 1 minute
|
|
max: 10,
|
|
message: { status: 'error', message: 'Discovery rate limited — max 10 per minute' },
|
|
standardHeaders: true,
|
|
legacyHeaders: false,
|
|
}));
|
|
|
|
// --- Body parsing ---
|
|
app.use(express.json({ limit: '1mb' }));
|
|
|
|
// --- Observability (observability-monitoring skill) ---
|
|
app.use(requestLogger);
|
|
|
|
// --- Health check ---
|
|
app.get('/api/health', (_req, res) => {
|
|
res.json({
|
|
status: 'ok',
|
|
uptime: process.uptime(),
|
|
timestamp: new Date().toISOString(),
|
|
connections: io.engine.clientsCount,
|
|
});
|
|
});
|
|
|
|
// --- Debug endpoint (dev only) ---
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
app.get('/api/debug-config', async (_req, res) => {
|
|
const hosts = await getHostConfigs();
|
|
res.json({ hosts });
|
|
});
|
|
}
|
|
|
|
// --- Public Routes ---
|
|
app.use('/api', authRouter);
|
|
|
|
// --- Protected Routes ---
|
|
app.use('/api', requireAuth, discoverRouter);
|
|
app.use('/api', requireAuth, configRouter);
|
|
app.use('/api', requireAuth, statsRouter);
|
|
app.use('/api', requireAuth, filesRouter);
|
|
app.use('/api', requireAuth, terminalRouter);
|
|
app.use('/api', requireAuth, topologyRouter);
|
|
app.use('/api', requireAuth, hostsRouter);
|
|
|
|
// --- Global error handler (must be last) ---
|
|
app.use(errorHandler);
|
|
|
|
// --- Start server ---
|
|
httpServer.listen(PORT, () => {
|
|
logger.info(`Server running on http://localhost:${PORT}`);
|
|
});
|