File size: 3,584 Bytes
4318319
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
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}`);
});