import http from 'node:http'; import { randomBytes, timingSafeEqual } from 'node:crypto'; import { readFile } from 'node:fs/promises'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; // Expose only built assets and the application API, with a temporary password. const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../apps/web/dist'); const password = process.env.PREVIEW_PASSWORD || randomBytes(12).toString('base64url'); const credential = Buffer.from('Basic ' + Buffer.from(`beryl:${password}`).toString('base64')); const types = { '.html': 'text/html; charset=utf-8', '.js': 'text/javascript', '.css': 'text/css', '.svg': 'image/svg+xml', '.png': 'image/png', '.woff2': 'font/woff2' }; http.createServer(async (req, res) => { const supplied = Buffer.from(req.headers.authorization || ''); if (supplied.length !== credential.length || !timingSafeEqual(supplied, credential)) { res.writeHead(401, { 'WWW-Authenticate': 'Basic realm="Beryl preview"', 'Cache-Control': 'no-store' }); return res.end('Authentication required'); } if (req.headers.origin && req.headers.origin !== `https://${req.headers.host}` && req.headers.origin !== `http://${req.headers.host}`) { res.writeHead(403); return res.end('Origin denied'); } let pathname; try { pathname = decodeURIComponent(new URL(req.url, 'http://localhost').pathname); } catch { res.writeHead(400); return res.end(); } if (pathname.startsWith('/api/') || pathname === '/health') { const headers = { ...req.headers, host: 'localhost:3001' }; delete headers.authorization; const upstream = http.request({ hostname: '127.0.0.1', port: 3001, path: req.url, method: req.method, headers }, response => { const safeHeaders = { ...response.headers, 'cache-control': 'no-store' }; delete safeHeaders['access-control-allow-origin']; res.writeHead(response.statusCode, safeHeaders); response.pipe(res); }); upstream.on('error', () => { if (!res.headersSent) res.writeHead(502); res.end('Backend unavailable'); }); res.on('close', () => upstream.destroy()); req.pipe(upstream); return; } if (!['GET', 'HEAD'].includes(req.method)) { res.writeHead(405); return res.end(); } const relative = pathname === '/' ? 'index.html' : pathname.slice(1); const file = path.resolve(root, relative); if (!file.startsWith(root + path.sep) || relative.split('/').some(p => p.startsWith('.'))) { res.writeHead(404); return res.end(); } try { const data = await readFile(file); res.writeHead(200, { 'Content-Type': types[path.extname(file)] || 'application/octet-stream', 'X-Content-Type-Options': 'nosniff', 'Cache-Control': 'no-store' }); res.end(req.method === 'HEAD' ? undefined : data); } catch { res.writeHead(404); res.end('Not found'); } }).listen(4173, '127.0.0.1', () => { console.log('Preview listening on 127.0.0.1:4173'); console.log('Username: beryl'); console.log('Password: ' + password); });