> ## Documentation Index
> Fetch the complete documentation index at: https://developers.googa.com.br/llms.txt
> Use this file to discover all available pages before exploring further.

# Modelo de autenticação sobre Supabase

> Como identidade de leitor, identidade de parceiro e domínio customizado se combinam em cima do Supabase.

## Por que isso não é trivial

Cada produto Googa hoje é servido por um projeto [Supabase](https://supabase.com) — 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](/security/child-data-eca)). 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](/novelaudio/overview)).
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

<AccordionGroup>
  <Accordion title="Custom Domains — mapear api.novelai.com.br para o projeto">
    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](/architecture/multi-tenant-model).

    ```
    api.novelai.com.br      ────▶ CNAME ──▶ Projeto Supabase "Novel" (<ref-novel>)
                                             atende NovelAI (texto) e NovelAudio (áudio)
    api.historinhai.com.br  ────▶ CNAME ──▶ Projeto Supabase "HistorinhAI" (<ref-historinhai>)
    api.googa.com.br        ────▶ CNAME ──▶ Projeto Supabase "Platform" (dedicado à API de parceiro)
    ```
  </Accordion>

  <Accordion title="SSO/OIDC — Supabase Auth como Relying Party do Minha Algar (planejado)">
    **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()`.
  </Accordion>

  <Accordion title="Webhooks de saída — Database Webhooks nativos">
    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](/platform/overview#eventos-webhooks-de-saída).
  </Accordion>

  <Accordion title="Isolamento multi-tenant — Row Level Security">
    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](/architecture/multi-tenant-model).
  </Accordion>
</AccordionGroup>

## 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.

<Steps>
  <Step title="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.
  </Step>

  <Step title="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.
  </Step>

  <Step title="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](/historinhai/overview)). **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.
  </Step>

  <Step title="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).
  </Step>
</Steps>

## Resumindo: quem autentica o quê

| Ator                                            | Como autentica                                                                 | Onde isso é validado                                                   |
| ----------------------------------------------- | ------------------------------------------------------------------------------ | ---------------------------------------------------------------------- |
| Leitor final (app oficial)                      | Supabase Auth (e-mail/senha, ou SSO Algar) → JWT de usuário                    | RLS no projeto do produto                                              |
| Sistema do parceiro (server-to-server)          | OAuth2 client credentials → JWT de serviço de curta duração                    | Partner Gateway (projeto Platform) valida; produto valida a assinatura |
| Webhook de saída (Googa → parceiro)             | Assinatura HMAC do payload (`X-Googa-Signature`)                               | Verificado pelo receptor (parceiro)                                    |
| Chamada simples de leitura via API key estática | *Planejado — não implementado* (a tabela de chaves existe; a verificação, não) | Partner Gateway (quando existir)                                       |

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.
