Guia

Receitas de integração

Copie estas receitas para qualquer projeto cliente (Lovable, Vite, PWA) para consumir o proxy opaco sem vazar o host upstream, nomes de rotas ou credenciais.

1. Configurar um tenant

Os tenants vivem na tabela proxy_tenants no banco de dados. Estrutura sugerida:

[
  {
    "id": "acme",
    "upstream": "https://xxxx.supabase.co",
    "allowedOrigins": ["https://app.acme.com"],
    "rateLimitPerMin": 120,
    "injectHeaders": {
      "apikey": "sb_publishable_...",
      "authorization": "Bearer sb_publishable_..."
    },
    "verbMap": {
      "0": { "prefix": "/rest/v1" },
      "1": { "prefix": "/auth/v1" },
      "2": { "prefix": "/storage/v1" },
      "3": { "prefix": "/functions/v1" }
    }
  }
]

2. Emitir um token opaco (do backend do seu cliente)

Nunca chame /admin/token do navegador: a chave de admin vazaria. Emita no seu servidor, envie o token opaco de curta duração para o navegador.

// Server-side
const res = await fetch("https://<this-proxy>/api/public/admin/token", {
  method: "POST",
  headers: {
    "content-type": "application/json",
    "x-admin-key": process.env.PROXY_ADMIN_KEY,
  },
  body: JSON.stringify({ tenantId: "acme", ttlSeconds: 300 }),
});
const { token } = await res.json();

3. Origem do cliente com máscara XOR (Vite)

Monte o host do proxy em tempo de execução a partir de bytes com máscara XOR para que nenhuma string simples apareça no bundle.

// src/lib/x/o.ts
const _k = [0x5a, 0x37, 0xc1, 0x09, 0x6e, 0xa2] as const;
// bytes = xor(host, _k) — precompute per-project
const _b = [/* ... */] as const;

function _x() {
  let s = "";
  for (let i = 0; i < _b.length; i++)
    s += String.fromCharCode(_b[i] ^ _k[i % _k.length]);
  return s;
}
const proto = String.fromCharCode(104,116,116,112,115,58,47,47);
export const origin = () => proto + _x();
export const gateway = () => origin() + "/api/public/e";

4. Mapa de verbos + wrapper de fetch

// src/lib/x/c.ts
import { gateway } from "./o";
const V = { rest: "0", auth: "1", storage: "2", fn: "3" } as const;
type Verb = keyof typeof V;

let _t: { v: string; exp: number } | null = null;
async function tok(): Promise<string> {
  if (_t && _t.exp - 10_000 > Date.now()) return _t.v;
  // proxy your own /api/mint endpoint — never call /admin/token from browser
  const r = await fetch("/api/mint", { method: "POST" });
  const d = await r.json();
  _t = { v: d.token, exp: d.expiresAt };
  return _t.v;
}

export async function fx(v: Verb, tail = "", init: RequestInit = {}) {
  const h = new Headers(init.headers || {});
  h.set("x-t", await tok());
  if (!h.has("content-type")) h.set("content-type", "application/json");
  return fetch(gateway() + "/" + V[v] + (tail ? "/" + tail : ""), {
    ...init, headers: h, credentials: "omit", cache: "no-store",
  });
}

5. Endurecimento do Vite (chunks anônimos + Terser)

// vite.config.ts (excerpt)
export default defineConfig({
  build: {
    sourcemap: false,
    minify: "terser",
    terserOptions: {
      compress: { drop_console: true, drop_debugger: true, passes: 2 },
      mangle: { toplevel: true, properties: { regex: /^_/ } },
      format: { comments: false },
    },
    rollupOptions: {
      output: {
        entryFileNames: "assets/[hash].js",
        chunkFileNames: "assets/[hash].js",
        assetFileNames: "assets/[hash][extname]",
        manualChunks(id) {
          if (id.includes("src/lib/x")) return "x";
        },
      },
    },
  },
});

6. Anti-cache PWA

Sirva /version.json com no-store; em caso de divergência, carregue /api/public/sw-kill.js deste proxy para limpar SW obsoletos + caches.

// on app boot
const local = "1.0.0";
fetch("/version.json", { cache: "no-store" })
  .then((r) => r.json())
  .then((d) => {
    if (d.version !== local) {
      const s = document.createElement("script");
      s.src = "https://<this-proxy>/api/public/sw-kill.js";
      document.head.appendChild(s);
    }
  });

7. Scanner de vazamentos em CI

# .github/workflows/scan.yml (or run locally after build)
bun run build
rg -i "supabase\\.co" dist && exit 1 || true
rg -i "functions/v1" dist && exit 1 || true
rg -i "service_role|sk_live|sk_test" dist && exit 1 || true
find dist -name "*.map" | grep . && exit 1 || true

Recursos deste self-host

  • Rate limit persistente via PostgreSQL.
  • Suporte total a WebSocket e Streaming (SSE).
  • Trilha de auditoria completa e logs estruturados.
  • Gerenciamento de tenants via painel administrativo.