← back to Claude Webdev Accelerator

scripts/gate-proxy.js

35 lines

// Zero-dep Basic-auth reverse proxy for public previews.
// Locks the ENTIRE upstream app behind one credential so a public tunnel URL
// requires auth regardless of how the app scopes its own auth.
// Usage: PORT=4901 UPSTREAM=127.0.0.1:3901 GATE_AUTH=admin:DW2024! node gate-proxy.js
const http = require('http');
const PORT = parseInt(process.env.PORT || '4900', 10);
const UPSTREAM = process.env.UPSTREAM || '127.0.0.1:3900';
const GATE_AUTH = process.env.GATE_AUTH || 'admin:DW2024!';
const [UHOST, UPORT] = UPSTREAM.split(':');

function authed(req) {
  const m = (req.headers.authorization || '').match(/^Basic\s+(.+)$/i);
  if (!m) return false;
  try { return Buffer.from(m[1], 'base64').toString() === GATE_AUTH; } catch { return false; }
}

http.createServer((req, res) => {
  if (!authed(req)) {
    res.writeHead(401, { 'WWW-Authenticate': 'Basic realm="preview"' });
    return res.end('preview: auth required');
  }
  const opts = {
    host: UHOST, port: parseInt(UPORT, 10), method: req.method, path: req.url,
    headers: { ...req.headers, host: `${UHOST}:${UPORT}` },
  };
  const up = http.request(opts, (ur) => {
    res.writeHead(ur.statusCode, ur.headers);
    ur.pipe(res);
  });
  up.on('error', (e) => { res.writeHead(502); res.end('upstream error: ' + e.message); });
  req.pipe(up);
}).listen(PORT, '127.0.0.1', function () {
  console.log(`[gate-proxy] :${PORT} -> ${UPSTREAM} (gated)`);
});