Grabby-Voice-Admin / server.js
moonlantern1's picture
Deploy protected admin proxy
4318319 verified
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}`);
});