const http = require('node:http'); const { Buffer } = require('node:buffer'); const port = Number(process.env.PORT || 7860); const upstreamBase = process.env.UPSTREAM_BASE_URL || 'https://moonlantern1-grabby-voice.hf.space'; function basicHeader(username, password) { return 'Basic ' + Buffer.from(`${username}:${password}`).toString('base64'); } function parseBasicAuth(header) { if (!header || !header.startsWith('Basic ')) return null; try { const decoded = Buffer.from(header.slice('Basic '.length), 'base64').toString('utf8'); const splitAt = decoded.indexOf(':'); if (splitAt < 0) return null; return { username: decoded.slice(0, splitAt), password: decoded.slice(splitAt + 1) }; } catch { return null; } } function unauthorized(res) { res.writeHead(401, { 'WWW-Authenticate': 'Basic realm="Grabby Voice Admin", charset="UTF-8"', 'Content-Type': 'text/plain; charset=utf-8', }); res.end('Authentication required'); } function isAuthorized(req) { const expectedUsername = process.env.ADMIN_USERNAME; const expectedPassword = process.env.ADMIN_PASSWORD; if (!expectedUsername || !expectedPassword) return false; const credentials = parseBasicAuth(req.headers.authorization); return credentials?.username === expectedUsername && credentials?.password === expectedPassword; } function copyRequestHeaders(req) { const headers = new Headers(); for (const [key, value] of Object.entries(req.headers)) { if (!value) continue; const lower = key.toLowerCase(); if (['host', 'connection', 'content-length', 'authorization'].includes(lower)) continue; headers.set(key, Array.isArray(value) ? value.join(', ') : value); } const upstreamUser = process.env.UPSTREAM_ADMIN_USERNAME || process.env.ADMIN_USERNAME; const upstreamPass = process.env.UPSTREAM_ADMIN_PASSWORD || process.env.ADMIN_PASSWORD; if (upstreamUser && upstreamPass) headers.set('authorization', basicHeader(upstreamUser, upstreamPass)); return headers; } function copyResponseHeaders(upstream) { const headers = {}; upstream.headers.forEach((value, key) => { const lower = key.toLowerCase(); if (['connection', 'content-encoding', 'content-length', 'transfer-encoding'].includes(lower)) return; headers[key] = value; }); return headers; } const server = http.createServer(async (req, res) => { if (!isAuthorized(req)) { unauthorized(res); return; } if (req.url === '/' || req.url === '') { res.writeHead(302, { Location: '/admin/reviews' }); res.end(); return; } if (!['GET', 'HEAD'].includes(req.method || 'GET')) { res.writeHead(405, { 'Content-Type': 'text/plain; charset=utf-8' }); res.end('Method not allowed'); return; } try { const target = new URL(req.url || '/', upstreamBase); const upstream = await fetch(target, { method: req.method, headers: copyRequestHeaders(req), redirect: 'follow', }); res.writeHead(upstream.status, copyResponseHeaders(upstream)); if (req.method === 'HEAD' || !upstream.body) { res.end(); return; } const reader = upstream.body.getReader(); while (true) { const { done, value } = await reader.read(); if (done) break; res.write(Buffer.from(value)); } res.end(); } catch (error) { console.error(error); res.writeHead(502, { 'Content-Type': 'text/plain; charset=utf-8' }); res.end('Admin upstream is unavailable'); } }); server.listen(port, '0.0.0.0', () => { console.log(`Grabby admin proxy listening on ${port}`); });