// Reverse proxy with built-in password auth for HermesFace Spaces. // Routes: /v1/* → gateway:8642 (API key gated), /* → dashboard:7861 (password gated). // Uses POST-based login (NO client crypto — works in HF iframes). // Drop into scripts/proxy.js. No npm install needed — Node built-in http module only. const http = require('http'); const crypto = require('crypto'); const DASHBOARD = { host: '127.0.0.1', port: 7861 }; const GATEWAY = { host: '127.0.0.1', port: 8642 }; const PASSWORD = process.env.HERMES_WEBUI_PASSWORD || 'gogiantswin'; const AUTH_HASH = crypto.createHash('sha256').update(PASSWORD).digest('hex').slice(0, 16); function isAuthenticated(req) { const cookies = (req.headers.cookie || '').split(';').map(c => c.trim()); for (const c of cookies) { const [name, val] = c.split('='); if (name === 'hf_auth' && val === AUTH_HASH) return true; } return false; } function getTarget(url) { return url.startsWith('/v1/') || url === '/v1' ? GATEWAY : DASHBOARD; } function serveLogin(res, error) { const errHtml = error ? '
Wrong password
' : ''; res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); res.end(` HermesFace — Login

🔱 HermesFace

Enter password to continue

${errHtml}
`); } function parseBody(req, cb) { let body = ''; req.on('data', chunk => { body += chunk; if (body.length > 1024) req.destroy(); }); req.on('end', () => { const params = {}; body.split('&').forEach(p => { const [k,v] = p.split('='); params[k] = decodeURIComponent(v || ''); }); cb(params); }); } const server = http.createServer((req, res) => { // Handle login POST if (req.method === 'POST' && req.url === '/_login') { parseBody(req, (params) => { if (params.pw === PASSWORD) { res.writeHead(302, { 'Location': '/', 'Set-Cookie': `hf_auth=${AUTH_HASH}; Path=/; Max-Age=86400; SameSite=Lax` }); res.end(); } else { serveLogin(res, true); } }); return; } const target = getTarget(req.url); // Auth check for dashboard routes (NOT /v1) if (target === DASHBOARD && !isAuthenticated(req)) { return serveLogin(res, false); } // Rewrite Host header so dashboard accepts forwarded requests req.headers.host = `${target.host}:${target.port}`; const proxyReq = http.request({ hostname: target.host, port: target.port, path: req.url, method: req.method, headers: req.headers, }, (proxyRes) => { res.writeHead(proxyRes.statusCode, proxyRes.headers); proxyRes.pipe(res); }); proxyReq.on('error', (err) => { console.error(`[proxy] ${req.method} ${req.url} → ${target.host}:${target.port} ERROR: ${err.message}`); try { res.writeHead(502); res.end('Bad Gateway'); } catch (_) {} }); req.pipe(proxyReq); }); // WebSocket upgrade server.on('upgrade', (req, socket, head) => { const target = getTarget(req.url); if (target === DASHBOARD && !isAuthenticated(req)) { socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n'); socket.destroy(); return; } req.headers.host = `${target.host}:${target.port}`; const proxyWs = http.request({ hostname: target.host, port: target.port, path: req.url, method: 'GET', headers: req.headers }); proxyWs.on('upgrade', (proxyRes, proxySocket, proxyHead) => { socket.write('HTTP/1.1 101 Switching Protocols\r\n' + Object.entries(proxyRes.headers).map(([k,v]) => `${k}: ${v}`).join('\r\n') + '\r\n\r\n'); socket.pipe(proxySocket).pipe(socket); }); proxyWs.on('error', () => { socket.destroy(); }); proxyWs.end(); }); const PORT = process.env.PORT || 7860; server.listen(PORT, '0.0.0.0', () => { console.log(`[proxy] 0.0.0.0:${PORT} — /v1/* → gateway:8642 | /* → dashboard:7861 (password-protected)`); });