← back to Dw Boardroom Governance
frontend/serve.cjs
94 lines
// Static server with Basic Auth + API Proxy for Governance UI
const http = require('http');
const fs = require('fs');
const path = require('path');
const PORT = 4030;
const API_PORT = 4020;
const USER = 'admin';
const PASS = 'DWSecure2024!';
const DIST = path.join(__dirname, 'dist');
const MIME = {
'.html': 'text/html', '.js': 'application/javascript', '.css': 'text/css',
'.json': 'application/json', '.png': 'image/png', '.jpg': 'image/jpeg',
'.svg': 'image/svg+xml', '.woff2': 'font/woff2', '.woff': 'font/woff',
};
const server = http.createServer((req, res) => {
const auth = req.headers.authorization;
if (!auth || !auth.startsWith('Basic ')) {
res.writeHead(401, { 'WWW-Authenticate': 'Basic realm="DW Governance"' });
return res.end('Unauthorized');
}
const [u, p] = Buffer.from(auth.slice(6), 'base64').toString().split(':');
if (u !== USER || p !== PASS) {
res.writeHead(401, { 'WWW-Authenticate': 'Basic realm="DW Governance"' });
return res.end('Unauthorized');
}
// ── API Proxy: Forward /api/* requests to backend on port 4020 ──
if (req.url.startsWith('/api/')) {
const proxyOpts = {
hostname: '127.0.0.1',
port: API_PORT,
path: req.url,
method: req.method,
headers: { ...req.headers, host: `127.0.0.1:${API_PORT}` },
};
const proxyReq = http.request(proxyOpts, (proxyRes) => {
res.writeHead(proxyRes.statusCode, proxyRes.headers);
proxyRes.pipe(res);
});
proxyReq.on('error', (err) => {
console.error('[Proxy] Error forwarding to API:', err.message);
res.writeHead(502, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'API backend unavailable', detail: err.message }));
});
// Forward request body for POST/PUT/PATCH
req.pipe(proxyReq);
return;
}
// ── WebSocket upgrade: Forward /ws to backend ──
// (handled by 'upgrade' event below)
// ── Static file serving ──
let filePath = path.join(DIST, req.url === '/' ? 'index.html' : req.url);
if (!fs.existsSync(filePath)) filePath = path.join(DIST, 'index.html');
const ext = path.extname(filePath);
const mime = MIME[ext] || 'application/octet-stream';
try {
const content = fs.readFileSync(filePath);
res.writeHead(200, { 'Content-Type': mime });
res.end(content);
} catch {
res.writeHead(404);
res.end('Not found');
}
});
// ── WebSocket Proxy: Forward upgrade requests to backend ──
const net = require('net');
server.on('upgrade', (req, socket, head) => {
if (!req.url.startsWith('/ws')) return socket.destroy();
const proxy = net.connect(API_PORT, '127.0.0.1', () => {
proxy.write(
`${req.method} ${req.url} HTTP/1.1\r\n` +
Object.entries(req.headers).map(([k, v]) => `${k}: ${v}`).join('\r\n') +
'\r\n\r\n'
);
if (head && head.length) proxy.write(head);
socket.pipe(proxy).pipe(socket);
});
proxy.on('error', () => socket.destroy());
socket.on('error', () => proxy.destroy());
});
server.listen(PORT, '0.0.0.0', () => console.log(`[Governance UI] Serving on port ${PORT} with auth + API proxy to ${API_PORT}`));