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