Skip to main content

Por que isso não é trivial

Cada produto Googa hoje é servido por um projeto Supabase — Postgres com Row Level Security (RLS), Supabase Auth e Edge Functions. Isso resolve muito bem um problema: autenticar o usuário final (o leitor) dentro do próprio app oficial. A API de parceiro precisa resolver um problema diferente e mais amplo:
  1. Duas identidades, não uma. O parceiro (ex: o backend da Algar) autentica como sistema, não como pessoa — client credentials, sem sessão de navegador. O assinante final autentica como pessoa, via SSO federado pelo “Minha Algar”. São dois fluxos de auth completamente diferentes que convergem na mesma API.
  2. Multi-projeto. NovelAI e NovelAudio já compartilham um projeto Supabase; HistorinhAI roda em outro, isolado — decisão deliberada, dado o tratamento de dados de crianças (ver Proteção de dados de menores). A API de parceiro precisa apresentar uma superfície única em três domínios — api.googa.com.br, api.novelai.com.br (que também atende o NovelAudio) e api.historinhai.com.br — sem expor essa fragmentação para quem integra. Não existe api.novelaudio.com.br (ver NovelAudio).
  3. Domínio próprio, não *.supabase.co. Nenhum parceiro enterprise aceita apontar para o domínio de um fornecedor de infra. O desenho usa api.novelai.com.br, api.historinhai.com.br e api.googa.com.br, com TLS e sem redirect visível.

Capacidades nativas do Supabase

O plano Supabase Pro (e superiores) inclui Custom Domains: você aponta um CNAME do seu domínio para <project-ref>.supabase.co, o Supabase provisiona e renova o certificado TLS automaticamente, e a partir daí tanto PostgREST (/rest/v1) quanto Edge Functions (/functions/v1) e Auth (/auth/v1) passam a responder sob o domínio próprio.Para os três domínios de API, isso significa: api.novelai.com.br é o único backend físico tanto para NovelAI quanto para NovelAudio — os dois apps já compartilham o mesmo projeto Supabase hoje (“Novel”), então não faz sentido criar um api.novelaudio.com.br separado só para bater na mesma infraestrutura; a Edge Function resolve por rota/recurso (texto vs. áudio), não por domínio. api.historinhai.com.br aponta para o projeto Supabase próprio do HistorinhAI — isolado de propósito. api.googa.com.br (Platform API) usa um projeto Supabase dedicado, só para a camada de parceiro — ver Multi-tenant.
Planejado — ainda não implementado: nenhum IdP de parceiro está configurado hoje.Supabase Auth suporta provedores OIDC customizados (além dos sociais padrão). Para o fluxo “login federado pelo Minha Algar, sem nova senha”, o desenho correto é: o Minha Algar é o Identity Provider (IdP), e o Supabase Auth de cada produto atua como Relying Party (RP) — exatamente o papel para o qual foi construído.Fluxo (/oauth2/authorize do lado da Algar):
  1. O assinante toca “Ler novela” dentro do app Minha Algar.
  2. Minha Algar redireciona para api.novelai.com.br/auth/v1/authorize?provider=algar (endpoint nativo do Supabase Auth, configurado com o Client ID/Secret do provedor OIDC da Algar).
  3. Supabase Auth troca o authorization_code pelo id_token da Algar, valida a assinatura, e cria (ou recupera) o usuário Supabase correspondente — o sub do token da Algar fica gravado em auth.identities, e o subscriber_id da Algar vai para app_metadata.
  4. O app recebe uma sessão Supabase normal (JWT access_token/refresh_token) — daqui em diante, RLS funciona exatamente como para um usuário que criou conta com e-mail/senha.
Isso é configuração, não código novo — o esforço real está do lado da Algar expor um endpoint OIDC padrão, e do nosso lado mapear app_metadata.algar_subscriber_id nas políticas de RLS que hoje já usam auth.uid().
Os eventos que o parceiro precisa (subscriber.updated, subscription.canceled) nascem de mudanças na tabela de entitlements de parceiro. Supabase tem Database Webhooks nativos: um trigger Postgres (pg_net) dispara uma chamada HTTP assíncrona sempre que uma linha muda. O desenho implementado encadeia isso a uma função intermediária de despacho (webhook-dispatch, projeto Platform), que assina o payload com HMAC-SHA256 (X-Googa-Signature, X-Googa-Event) e entrega ao webhook_url do parceiro em uma única tentativa — o retry nativo do Database Webhook cobre falha ao invocar a função de despacho, mas não existe fila própria de reentrega ao endpoint do parceiro. Não precisa de Kafka nem de um broker separado para o volume de eventos de um parceiro de billing — é a ferramenta certa para o tamanho do problema. Ver a verificação de assinatura em Platform API — Eventos.
RLS já é a linha de defesa principal hoje (cada leitor só vê as próprias linhas). Para a API de parceiro, o desenho implementado é diferente do RLS por política do leitor: as tabelas de domínio de parceiro (partners, partner_credentials, partner_subscribers…) têm RLS habilitado com zero policies — nenhum acesso por anon/authenticated; só as Edge Functions do Partner Gateway, via service_role, as tocam. O isolamento entre parceiros é aplicado dentro dessas functions: toda consulta filtra pelo partner_id extraído do JWT de serviço verificado — nunca por um valor enviado pelo cliente na própria requisição. Ver Modelo multi-tenant.

Partner Gateway — a camada que conecta as duas pontas

Supabase não oferece, nativamente, um servidor de autorização OAuth2 para emitir credenciais a outros sistemas (client credentials grant) — ele é um IdP para pessoas, não uma central de emissão de chaves de API para máquinas. Isso é esperado: nenhum BaaS genérico resolve isso, porque é uma decisão de produto, não de infraestrutura. O Partner Gateway é a camada fina construída por cima para resolver exatamente isso.
1

Tabela partner_credentials + partner_id em todo domínio de negócio

Um projeto Supabase dedicado (“Platform”) guarda partners (com webhook_url/webhook_secret de cada parceiro), partner_credentials (client_id, hash do client_secret, escopos permitidos, status) e partner_api_keys (chave rotacionável para leituras simples — a tabela existe, mas a verificação por API key ainda não está implementada em nenhum endpoint). Nenhum dado de leitor mora aqui — só metadado de integração.
2

Edge Function 'Partner Gateway' — troca client credentials por um JWT de curta duração

Uma Edge Function em api.googa.com.br/oauth2/token implementa o grant client_credentials do OAuth2 (RFC 6749 §4.4): recebe client_id + client_secret, valida contra partner_credentials, e devolve um JWT assinado com os escopos do parceiro e TTL curto (10 minutos). Esse JWT é o que autentica as chamadas seguintes a Entitlements/Billing — ele nunca é um JWT de usuário Supabase, é um JWT de serviço, verificado pelas outras Edge Functions via a chave pública do projeto Platform.
3

Roteamento cross-projeto

Como NovelAI/NovelAudio e HistorinhAI vivem em projetos Supabase diferentes, o Partner Gateway (projeto Platform) não acessa os dados diretamente — ele chama as Edge Functions de domínio de cada produto (entitlements-novelai, entitlements-historinhai) passando o JWT de serviço junto, e cada produto valida esse JWT contra a chave pública do Platform antes de aplicar sua própria lógica de RLS/negócio local. Isso mantém o isolamento entre produtos sem duplicar a lógica de autenticação de parceiro em cada projeto.Exceção deliberada (planejada — ainda não implementada) — HistorinhAI emitir a própria credencial. Para o produto que trata dados de crianças, o desenho de destino vai um passo além: api.historinhai.com.br teria seu próprio emissor de token e seu próprio par de chaves de assinatura, independentes do Platform Gateway, servindo o futuro endpoint de engajamento (ver API HistorinhAI). Hoje isso não existe: o HistorinhAI valida o token emitido pelo Platform (mesma chave pública), e o emissor próprio foi deliberadamente adiado para uma revisão dedicada. Quando existir, será defesa em profundidade: um comprometimento da chave de assinatura do Platform não dá acesso a nenhuma credencial do HistorinhAI, e vice-versa — ao custo de um cadastro de credencial duplicado por parceiro, um trade-off aceito dado o que está em jogo nesse produto especificamente.
4

Rate-limit & WAF na borda

Nem PostgREST nem Edge Functions têm rate-limit configurável por parceiro/rota fora do que o Supabase já aplica para proteção própria da plataforma. A camada de rate-limit por parceiro (e WAF) fica na borda, antes do Custom Domain — um proxy leve (Cloudflare na frente do CNAME é a opção mais simples: regras de rate-limit por token de API, sem mudar nada do lado do Supabase).

Resumindo: quem autentica o quê

Nenhuma dessas quatro linhas exige trocar o Supabase por outra coisa — todas são construídas em cima do que já existe, com uma única peça nova (o Partner Gateway) fazendo a ponte entre “autenticação de máquina” e o modelo de “autenticação de pessoa” que o Supabase já resolve bem.