// DSH LAN proxy (Host/Origin rewrite mode) // Forwards 0.0.0.0:3081 -> 127.0.0.1:3080 (HTTP + WebSocket upgrade) // Rewrites Host and Origin to the loopback authority so requests pass the // DSH API trust fence (Host must be loopback or a trustedHosts entry). // Cookie authority stays consistent because it is derived from the request // Host the server sees, which is always 127.0.0.1:3080 through this proxy. import http from 'node:http'; const LISTEN_PORT = 3081; const LISTEN_HOST = '0.0.0.0'; const UPSTREAM_HOST = '127.0.0.1'; const UPSTREAM_PORT = 3080; const UPSTREAM_AUTHORITY = `${UPSTREAM_HOST}:${UPSTREAM_PORT}`; const UPSTREAM_ORIGIN = `http://${UPSTREAM_AUTHORITY}`; function rewriteHeaders(req) { const h = { ...req.headers }; h['host'] = UPSTREAM_AUTHORITY; if (h['origin'] !== undefined) h['origin'] = UPSTREAM_ORIGIN; h['x-forwarded-for'] = req.socket.remoteAddress || ''; h['x-forwarded-host'] = req.headers.host || ''; h['x-forwarded-proto'] = 'http'; return h; } const server = http.createServer((req, res) => { const proxyReq = http.request( { host: UPSTREAM_HOST, port: UPSTREAM_PORT, path: req.url, method: req.method, headers: rewriteHeaders(req), }, (proxyRes) => { res.writeHead(proxyRes.statusCode || 502, proxyRes.headers); proxyRes.pipe(res); } ); proxyReq.on('error', (err) => { if (!res.headersSent) res.writeHead(502, { 'Content-Type': 'text/plain; charset=utf-8' }); res.end(`DSH LAN proxy: upstream(${UPSTREAM_AUTHORITY}) error: ${err.message}`); }); req.pipe(proxyReq); }); server.on('upgrade', (req, socket, head) => { const proxyReq = http.request({ host: UPSTREAM_HOST, port: UPSTREAM_PORT, path: req.url, method: req.method, headers: rewriteHeaders(req), }); proxyReq.on('upgrade', (proxyRes, proxySocket, proxyHead) => { const lines = ['HTTP/1.1 101 Switching Protocols']; for (const [k, v] of Object.entries(proxyRes.headers)) lines.push(`${k}: ${v}`); socket.write(lines.join('\r\n') + '\r\n\r\n'); if (proxyHead && proxyHead.length) socket.write(proxyHead); proxySocket.pipe(socket); socket.pipe(proxySocket); proxySocket.on('error', () => socket.destroy()); socket.on('error', () => proxySocket.destroy()); }); proxyReq.on('response', (proxyRes) => { const lines = [`HTTP/1.1 ${proxyRes.statusCode} ${proxyRes.statusMessage || ''}`]; for (const [k, v] of Object.entries(proxyRes.headers)) lines.push(`${k}: ${v}`); socket.end(lines.join('\r\n') + '\r\n\r\n'); proxyRes.pipe(socket); }); proxyReq.on('error', () => socket.destroy()); proxyReq.end(head && head.length ? head : undefined); }); server.listen(LISTEN_PORT, LISTEN_HOST, () => { console.log(`[dsh-lan-proxy] ${LISTEN_HOST}:${LISTEN_PORT} -> ${UPSTREAM_AUTHORITY} (Host/Origin rewritten)`); });