Spaces:
Sleeping
Sleeping
File size: 4,972 Bytes
844042d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | // 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)`);
});
|