Cloudflare Worker — OLP Gateway v2
Última atualização: 2026-02-23 Código-fonte: Gerenciado externamente no painel Cloudflare (não faz parte deste repositório) URL de produção:
https://gateway.olp.digital
1. Por Que Existe
O Worker resolve dois problemas que o Supabase sozinho não consegue:
| Problema | Causa | Solução do Worker |
|---|---|---|
| Cookies cross-origin | Edge Functions do Supabase estão em *.supabase.co, domínio diferente de olp.digital. Set-Cookie com SameSite=None exige Domain correto | Reescreve Domain dos cookies olp_auth e olp_mural para .olp.digital |
| Geolocalização | Supabase não expõe dados de geo do cliente. logging-helper.ts precisa de cidade/estado/país para auditoria | Injeta headers X-Geo-* extraídos de request.cf (dados nativos do Cloudflare) |
Sem o Worker: Login funciona apenas em localhost/preview (cookies first-party). Em produção, cookies não são aceitos pelo browser.
2. Ativação e Roteamento (Frontend)
O uso do Worker é opcional e controlado por variáveis de ambiente no .env:
# Ativar gateway (produção)
VITE_USE_WORKER=true
VITE_WORKER_URL=https://gateway.olp.digital
# Desativar gateway (desenvolvimento/preview)
VITE_USE_WORKER=false
# ou simplesmente não definir as variáveisLógica no src/lib/edge-function.ts
if (VITE_USE_WORKER === 'true' && VITE_WORKER_URL) {
baseUrl = VITE_WORKER_URL // gateway.olp.digital
} else {
baseUrl = VITE_SUPABASE_URL // *.supabase.co (direto)
}Quando Usar Cada Modo
| Contexto | VITE_USE_WORKER | Motivo |
|---|---|---|
localhost:5173 | false | Cookies first-party funcionam naturalmente |
| Preview Lovable | false | Cookies first-party, sem necessidade de reescrita |
Produção (olp.digital) | true | Necessário para cookies cross-origin e geolocalização |
3. Fluxo de Requisição
┌─────────────┐ ┌──────────────────────┐ ┌────────────────────┐
│ Frontend │────▶│ gateway.olp.digital │────▶│ *.supabase.co │
│ olp.digital │ │ (Cloudflare Worker) │ │ Edge Functions │
└─────────────┘ └──────────────────────┘ └────────────────────┘
│ │
┌──────┴──────┐ ┌──────┴──────┐
│ Injeta: │ │ Retorna: │
│ X-Geo-City │ │ Set-Cookie │
│ X-Geo-Region│ │ JSON body │
│ X-Geo-Country│ └─────────────┘
│ X-Geo-Timezone│
│ X-Real-IP │
│ X-Forwarded-For│
│ X-Gateway │
└─────────────┘
│
┌──────┴──────┐
│ Reescreve: │
│ Set-Cookie │
│ Domain= │
│ .olp.digital│
└─────────────┘4. Funcionalidades Detalhadas
4.1 Validação CORS
O Worker implementa sua própria validação CORS (independente do cors-helpers.ts das Edge Functions):
Origens permitidas (lista estática):
http://localhost:5173
http://localhost:8080
https://olp.digitalPadrões dinâmicos (regex):
*.lovableproject.com
*.lovable.app
*.olp.digitalRegras:
- Origin presente + não permitido →
403 Forbidden - Origin ausente (curl/server-to-server) → Passa sem headers ACAO
- Origin válido → ACAO com origin exato +
credentials: true - Preflight (
OPTIONS) →204 No Contentcom headers CORS Max-Age: 86400(cache de preflight por 24h)
Nota: O Worker e as Edge Functions possuem listas CORS separadas. Ambas devem estar sincronizadas. A do Worker está no código do Worker (Cloudflare). A das Edge Functions está em
supabase/functions/_shared/cors-helpers.ts.
4.2 Reescrita de Cookies
Cookies monitorados: olp_auth, olp_mural
Lógica de reescrita por origem:
| Origem | Reescreve Domain? | Motivo |
|---|---|---|
localhost / 127.0.0.1 | ❌ | Dev — cookies first-party |
*.lovableproject.com | ❌ | Preview — cookies first-party |
*.lovable.app | ❌ | Preview — cookies first-party |
*.olp.digital | ✅ → .olp.digital | Produção — cross-origin necessário |
| Sem origin (curl) | ✅ → .olp.digital | Assume produção |
Detalhes técnicos:
- Usa
response.headers.getSetCookie()(array) em vez de.get('set-cookie')(string única) para capturar todos os headers Set-Cookie - Cookies de remoção (
Max-Age=0, usados no logout) não são reescritos — passam intactos - Cada cookie processado é adicionado via
headers.append()(nãoset()) para preservar múltiplos
4.3 Injeção de Geolocalização
Headers injetados a partir de request.cf (dados nativos do Cloudflare):
| Header | Fonte | Exemplo |
|---|---|---|
X-Geo-City | request.cf.city | São Paulo |
X-Geo-Region | request.cf.region | São Paulo |
X-Geo-Country | request.cf.country | BR |
X-Geo-Timezone | request.cf.timezone | America/Sao_Paulo |
X-OLP-Client-IP | CF-Connecting-IP | 200.xxx.xxx.xxx |
X-Real-IP | CF-Connecting-IP | 200.xxx.xxx.xxx |
X-Forwarded-For | CF-Connecting-IP | 200.xxx.xxx.xxx |
X-Gateway | Fixo | olp-gateway-v2 |
Nota:
X-OLP-Client-IPé o header preferido para captura de IP real. O Supabase não o sobrescreve, ao contrário deX-Real-IP. Ver IP_RETENTION_LGPD.md.
Consumo no backend: logging-helper.ts → extractGeoFromHeaders(req) lê os headers X-Geo-*, e extractIP(req) prioriza X-OLP-Client-IP.
4.4 Proxy de Headers
Headers preservados do request original (frontend → Supabase):
authorization, apikey, content-type, cookie, accept,
x-client-info, x-supabase-api-versionSe apikey não estiver presente, o Worker injeta SUPABASE_ANON_KEY automaticamente.
4.5 Timeout e Erros
| Cenário | Status | Mensagem |
|---|---|---|
| Timeout (>25s) | 504 | Gateway timeout - tente novamente |
| Erro de rede | 502 | Erro de conexão - tente novamente |
SUPABASE_URL não configurado | 500 | Gateway misconfigured |
Decisão de design (MVP): Sem fallback 503, sem retry automático. O frontend exibe o erro e o usuário pode tentar novamente.
4.6 Burst Rate Limiting (por IP)
Implementado em: 2026-03-08
O Worker rastreia requisições por IP usando um Map in-memory com sliding window de 5 segundos. Se um IP exceder 150 requests em 5s, o Worker injeta o header X-Burst-Blocked: true na requisição encaminhada ao Supabase.
Por que no Worker e não na Edge Function:
- Cloudflare Workers operam como processo único por edge location (sem fragmentação de isolates)
- Edge Functions do Supabase escalam em múltiplos isolates sob carga, cada um com seu próprio
Mapvazio - O Worker garante contagem cross-request precisa
Código a adicionar no Worker (Cloudflare Dashboard):
// ============= BURST RATE LIMITER =============
const BURST_LIMIT = { max: 150, windowMs: 5000 };
const burstTracker = new Map(); // IP -> timestamp[]
function checkBurst(ip) {
const now = Date.now();
const timestamps = (burstTracker.get(ip) || []).filter(t => now - t < BURST_LIMIT.windowMs);
timestamps.push(now);
burstTracker.set(ip, timestamps);
// Cleanup periódico
if (burstTracker.size > 10000) {
for (const [key, ts] of burstTracker) {
if (now - ts[ts.length - 1] > BURST_LIMIT.windowMs * 2) burstTracker.delete(key);
}
}
return timestamps.length <= BURST_LIMIT.max;
}
// No handler fetch(), ANTES de fazer o fetch ao Supabase:
// Apenas para rotas do portal-escola
const url = new URL(request.url);
if (url.pathname.includes('/portal-escola')) {
const clientIP = request.headers.get('CF-Connecting-IP') || 'unknown';
if (!checkBurst(clientIP)) {
headers.set('X-Burst-Blocked', 'true');
}
}Consumo na Edge Function (portal-escola/index.ts):
- Se
X-Burst-Blocked: true→ retorna 429 imediatamente - Se
X-Gateway: olp-gateway-v2(veio pelo Worker sem bloqueio) → pula burst check - Sem header de gateway (dev/preview) → usa fallback in-memory local
5. Variáveis de Ambiente do Worker (Cloudflare)
Configuradas no painel do Cloudflare Workers:
| Variável | Descrição |
|---|---|
SUPABASE_URL | URL do projeto Supabase (https://mjvuzsizjlcalyfmbquy.supabase.co) |
SUPABASE_ANON_KEY | Chave anon (fallback se frontend não enviar apikey) |
6. Sincronização Obrigatória
O Worker é um componente externo ao repositório. Alterações devem ser sincronizadas manualmente:
| Alteração | Onde atualizar |
|---|---|
| Nova origem permitida | Worker (ALLOWED_ORIGINS) E cors-helpers.ts |
| Novo cookie JWT | Worker (COOKIES_TO_REWRITE) E Edge Functions que emitem o cookie |
| Novo header preservado | Worker (PRESERVE_HEADERS) |
| Novo header de webhook | Worker (PRESERVE_HEADERS) E Edge Function que valida o header |
| Mudança de domínio | Worker (PRODUCTION_DOMAIN) E DNS |
⚠️ Webhooks externos (Wasender, Mercado Pago, etc.): Headers de autenticação customizados (ex:
x-webhook-secret,x-webhook-signature) devem ser adicionados explicitamente aoPRESERVE_HEADERSdo Worker. Caso contrário, o gateway remove esses headers antes de encaminhar ao Supabase, causando 401 na Edge Function. Alternativa: configurar webhooks para apontar diretamente à URL do Supabase (*.supabase.co), ignorando o gateway.
7. Como Editar o Worker
- Acessar Cloudflare Dashboard → Workers & Pages
- Selecionar o Worker
olp-gateway(ou nome configurado) - Editar no editor online ou fazer deploy via Wrangler CLI
- Testar com
curl:
# Teste básico (sem origin)
curl -v https://gateway.olp.digital/functions/v1/auth-diagnostics \
-H "Content-Type: application/json" \
-d '{"action":"public_smoke"}'
# Teste com origin de produção
curl -v https://gateway.olp.digital/functions/v1/me \
-H "Origin: https://olp.digital" \
-H "Content-Type: application/json" \
-H "Cookie: olp_auth=eyJ..."8. Troubleshooting
| Sintoma | Causa provável | Solução |
|---|---|---|
| Login funciona no preview mas não em produção | Worker não reescrevendo cookies | Verificar COOKIES_TO_REWRITE e shouldRewriteCookieDomain() |
| Geolocalização sempre "Portland, Oregon" | req não passado para registrarLog() | Adicionar req: req na chamada (ver AUDIT_LOG.md) |
403 em preflight | Origin não está na lista do Worker | Adicionar em ALLOWED_ORIGINS ou ORIGIN_PATTERNS |
504 Gateway timeout | Edge Function lenta (>25s) | Otimizar a Edge Function ou aumentar FETCH_TIMEOUT |
| Cookies não removidos no logout | Worker reescrevendo cookie de remoção | Bug — verificar detecção de Max-Age=0 |
| Logs sem geolocalização | Worker não injetando geo headers | Verificar request.cf e headers X-Geo-* |
Webhook retorna 401 | Header de autenticação do webhook removido pelo Worker | Adicionar header (ex: x-webhook-signature) ao PRESERVE_HEADERS |
9. Headers de Segurança HTTP
Implementado em: 2026-03-01 Status: ✅ Ativo em produção (verificado via
curl -I)
O Worker injeta headers de segurança em todas as respostas retornadas ao browser, via um objeto SECURITY_HEADERS aplicado antes do return.
9.1 Headers Ativos (Produção)
const SECURITY_HEADERS = {
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
'X-Frame-Options': 'DENY',
'X-Content-Type-Options': 'nosniff',
'Referrer-Policy': 'strict-origin-when-cross-origin',
'Permissions-Policy': 'camera=(), microphone=(), geolocation=(), payment=()',
'Content-Security-Policy': "default-src 'none'; frame-ancestors 'none'",
};| Header | Finalidade |
|---|---|
Strict-Transport-Security | Força HTTPS por 1 ano. Só funciona via HTTP header (meta tag é ignorada pela spec RFC 6797). |
X-Frame-Options | Previne Clickjacking (defesa em profundidade junto com CSP frame-ancestors). |
X-Content-Type-Options | Previne MIME sniffing em respostas JSON da API. |
Referrer-Policy | Controla vazamento de URL no header Referer. |
Permissions-Policy | Restringe APIs do browser (câmera, microfone, geolocalização, pagamento). |
Content-Security-Policy | CSP restritiva para API: default-src 'none' (nenhum recurso carregado) + frame-ancestors 'none' (não embute em iframe). Adequada porque o Worker só retorna JSON. |
9.2 Onde Estão no Código do Worker
Os headers são injetados após obter a resposta do Supabase e antes de retornar ao cliente:
// Aplicar headers de segurança em TODAS as respostas
for (const [key, value] of Object.entries(SECURITY_HEADERS)) {
newResponse.headers.set(key, value);
}9.3 CSP do Worker vs CSP do Frontend
| Contexto | CSP | Motivo |
|---|---|---|
| Worker (API) | default-src 'none'; frame-ancestors 'none' | Respostas são JSON — nenhum recurso deve ser carregado |
Frontend (index.html) | default-src 'self'; script-src 'self' 'unsafe-inline'; ... | Precisa carregar scripts, estilos, imagens, conectar a backends |
Não há conflito: O CSP do Worker aplica-se às respostas da API (gateway.olp.digital/functions/v1/...). O CSP do frontend aplica-se ao documento HTML servido pela Lovable.
9.4 Headers no index.html vs Worker
| Header | index.html (meta tag) | Worker (HTTP header) | Notas |
|---|---|---|---|
Content-Security-Policy | ✅ (permissiva para app) | ✅ (restritiva para API) | Políticas diferentes, ambas corretas |
X-Frame-Options | ✅ | ✅ | Defesa em profundidade |
X-Content-Type-Options | ✅ | ✅ | Cobre respostas de API também |
Strict-Transport-Security | ❌ Ignorado | ✅ Ativo | Só funciona via HTTP header |
Referrer-Policy | ✅ | ✅ | Defesa em profundidade |
Permissions-Policy | ✅ | ✅ | Defesa em profundidade |
9.5 Sobre HSTS Preload
O flag ; preload pode ser adicionado ao HSTS para submissão na HSTS Preload List:
| Ação | Reversível? | Efeito |
|---|---|---|
Adicionar ; preload no header do Worker | ✅ Sim (remove e faz deploy) | Apenas sinal de prontidão, sem efeito prático sozinho |
| Submeter em hstspreload.org | ❌ Praticamente não (3-6 meses para remover) | Browsers forçam HTTPS antes da primeira requisição |
Status atual: Sem ; preload. HSTS funciona normalmente — após a primeira visita HTTPS, o browser lembra por 1 ano. A janela mínima (primeira requisição HTTP) é mitigada pelo redirect 301 do Cloudflare.
Para adicionar preload no futuro:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preloadDepois submeter em hstspreload.org. Requer que todos os subdomínios suportem HTTPS.
10. Referências
- DEPLOYMENT.md — Visão geral de deploy e ambientes
- AUTHENTICATION.md — Fluxo JWT e cookies
- AUDIT_LOG.md —
registrarLog()comreq: req - EDGE_FUNCTIONS_CATALOG.md — Catálogo de funções consumidas via gateway
- SECURITY_AUDIT_2026-02-28.md — Auditoria de segurança (seção 9)
src/lib/edge-function.ts— Helper frontend que roteia via Workersupabase/functions/_shared/cors-helpers.ts— CORS das Edge Functions (sincronizar com Worker)supabase/functions/_shared/logging-helper.ts— Consumidor dos headersX-Geo-*