feat: add restricted Supabase reverse proxy for Timeweb

This commit is contained in:
pavlov346346-source 2026-09-11 15:56:35 +03:00
parent a206cfd1f1
commit 18ef6a2e45

74
proxy/server.js Normal file
View File

@ -0,0 +1,74 @@
import http from 'node:http';
import httpProxy from 'http-proxy';
const PORT = Number(process.env.PORT || 8080);
const TARGET = process.env.SUPABASE_TARGET || 'https://cksuehzcimitsxmeloes.supabase.co';
const allowedOrigins = new Set([
'https://app.caterium.ru',
'https://caterium.ru',
'https://www.caterium.ru'
]);
const proxy = httpProxy.createProxyServer({
target: TARGET,
changeOrigin: true,
xfwd: true,
secure: true,
ws: true
});
proxy.on('proxyReq', (proxyReq) => {
proxyReq.setHeader('host', new URL(TARGET).host);
});
proxy.on('error', (err, req, res) => {
console.error('[proxy:error]', err?.message || err);
if (res && !res.headersSent) {
res.writeHead(502, {'content-type':'application/json; charset=utf-8'});
}
try { res?.end(JSON.stringify({error:'upstream_unavailable'})); } catch (_) {}
});
function cors(req, res) {
const origin = req.headers.origin;
if (origin && !allowedOrigins.has(origin)) return false;
if (origin) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Vary', 'Origin');
}
res.setHeader('Access-Control-Allow-Credentials', 'true');
res.setHeader('Access-Control-Allow-Headers', req.headers['access-control-request-headers'] || 'authorization,apikey,content-type,x-client-info,prefer,range');
res.setHeader('Access-Control-Allow-Methods', 'GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS');
return true;
}
const server = http.createServer((req, res) => {
if (!cors(req, res)) {
res.writeHead(403, {'content-type':'application/json; charset=utf-8'});
return res.end(JSON.stringify({error:'origin_not_allowed'}));
}
if (req.method === 'OPTIONS') {
res.writeHead(204);
return res.end();
}
if (req.url === '/health' || req.url === '/healthz') {
res.writeHead(200, {'content-type':'application/json; charset=utf-8'});
return res.end(JSON.stringify({ok:true,target:'supabase'}));
}
if (!/^\/(rest|auth|storage|functions|realtime)\/v1(?:\/|\?|$)/.test(req.url || '')) {
res.writeHead(404, {'content-type':'application/json; charset=utf-8'});
return res.end(JSON.stringify({error:'route_not_allowed'}));
}
proxy.web(req, res);
});
server.on('upgrade', (req, socket, head) => {
const origin = req.headers.origin;
if (origin && !allowedOrigins.has(origin)) return socket.destroy();
if (!/^\/realtime\/v1(?:\/|\?|$)/.test(req.url || '')) return socket.destroy();
proxy.ws(req, socket, head);
});
server.listen(PORT, '0.0.0.0', () => {
console.log(`[caterium-proxy] listening on ${PORT} -> ${TARGET}`);
});