HermesFace / scripts /proxy.js
tkgray's picture
feat: reverse proxy with password auth (login gate + /v1 API routing)
844042d verified
Raw
History Blame Contribute Delete
4.97 kB
// 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 ? '<div style="color:#f85149;font-size:13px;margin-top:8px">Wrong password</div>' : '';
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end(`<!DOCTYPE html>
<html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>HermesFace β€” Login</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{display:flex;align-items:center;justify-content:center;min-height:100vh;background:#0f1117;font-family:-apple-system,BlinkMacSystemFont,sans-serif;color:#e1e4e8}
.box{background:#161b22;border:1px solid #30363d;border-radius:8px;padding:32px;width:340px;text-align:center}
h1{font-size:20px;margin-bottom:8px;color:#58a6ff}
p{font-size:14px;color:#8b949e;margin-bottom:20px}
input{width:100%;padding:10px;border:1px solid #30363d;border-radius:6px;background:#0d1117;color:#e1e4e8;font-size:14px;margin-bottom:12px;outline:none}
input:focus{border-color:#58a6ff}
button{width:100%;padding:10px;background:#238636;border:none;border-radius:6px;color:#fff;font-size:14px;cursor:pointer}
button:hover{background:#2ea043}
</style></head>
<body>
<div class="box">
<h1>πŸ”± HermesFace</h1>
<p>Enter password to continue</p>
<form method="POST" action="/_login">
<input type="password" name="pw" placeholder="Password" autofocus>
<button type="submit">Sign in</button>
${errHtml}
</form>
</div>
</body></html>`);
}
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)`);
});