Files
vscode-workbench/dsh-lan-proxy.mjs

78 lines
2.8 KiB
JavaScript

// 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)`);
});