← todos os posts

"Regra por linha, traduzida: o que RLS faz de verdade, com um exemplo de 3 usuários"

Ana, Bruno e Carla na mesma tabela. O que cada um vê com e sem regra, as três partes que toda regra tem e a ficha pra ler as regras do seu banco hoje.

"RLS desativado no Supabase."

Está na mesma lista de falhas comuns em app feito com IA que circulou entre programadores brasileiros. Já critiquei aqui a resposta que costuma vir junto, o "é só ligar o RLS", e expliquei o que a sigla é numa linha: Row-Level Security, regra por linha, a regra do banco que impede um usuário de ver o dado do outro.

Hoje é o passo seguinte: como ela funciona por dentro, com três pessoas de exemplo. Você não precisa escrever a regra, mas precisa conseguir ler. Senão a IA escreve uma, diz que está pronto, e você aceita o que não sabe conferir.

Por que a regra mora no banco, e não na tela

A tela é o cardápio: mostra o que o app escolhe mostrar. O banco é a cozinha, e quem pede direto na cozinha não passa pelo cardápio. Se só a tela filtra a lista, a cozinha entrega tudo pra quem pedir. Ontem fiz o mapa dos lugares onde essa checagem pode morar; o banco é o único em que ela vale pra qualquer pedido, venha de onde vier.

Os três usuários

Uma escola de inglês pequena tem um app com a tabela de boletins. Três pessoas usam:

  • Ana, aluna.
  • Bruno, aluno.
  • Carla, a dona da escola, que dá as notas.

A tabela tem quatro linhas. A coluna que importa é a última, "dono da linha":

Boletim Nota Dono da linha
Ana, março 8,5 Ana
Ana, abril 9,0 Ana
Bruno, março 6,0 Bruno
Bruno, abril 7,5 Bruno

O que cada um vê em três situações:

Sem regra por linha. A documentação do Supabase diz que uma tabela exposta sem RLS "pode ser lida e alterada por qualquer papel que tenha permissão sobre ela". Ana vê as quatro. Bruno vê as quatro. E consegue mudar a própria nota.

Regra ligada, nenhuma regra escrita. Ninguém vê nada, nem a Carla pelo app. É o "app ficou em branco" de quem ligou porque mandaram.

Regra ligada e escrita: "cada um vê as linhas em que é o dono". Ana vê duas. Bruno vê duas. Carla vê zero, porque nenhuma linha tem o nome dela. E a Carla precisa ver todas. Não é defeito: a regra faz exatamente o que diz. Cada tipo de acesso pede uma regra própria.

As três partes de toda regra

É assim que fica a regra da Ana e do Bruno, no exemplo da própria documentação do Supabase (troquei só o nome da tabela):

create policy "Cada aluno vê os próprios boletins"
on boletins for select
to authenticated
using ( (select auth.uid()) = dono_da_linha );

Não precisa decorar. Precisa achar três partes:

  1. Pra qual ação — for select. Select é ler. Os outros são insert (criar), update (alterar) e delete (apagar). Regra de leitura não diz nada sobre quem altera.
  2. Pra quem — to authenticated, quem fez login. Se estiver anon ou public, vale pra visitante sem login também.
  3. Qual linha — o using, que quer dizer "usando esta condição". auth.uid() é "quem está logado agora". A condição inteira lê: quem está logado é o dono desta linha.

Duas armadilhas que se escondem na terceira parte.

O true. Se a condição é só using (true), ela quer dizer "sempre sim". A regra existe, o painel para de reclamar, e todo mundo continua vendo tudo. É o atalho clássico da IA pra "fazer funcionar".

O with check. Em regra de criar ou alterar aparece uma segunda condição. A documentação explica: o using decide em quais linhas existentes a ação pode mexer; o with check confere a linha nova que vai ser gravada. Sem ele, Bruno pode criar um boletim e pôr o nome da Ana como dono.

E a Carla ganha uma regra própria: "quem tem papel de professora vê todas as linhas". Se esse papel ficar num campo que o próprio usuário edita, Bruno se declara professor; é a armadilha do post de ontem.

O que fazer hoje: a ficha de leitura

Vinte minutos, se o seu app usa Supabase.

Passo 1. No painel do Supabase, abra a lista de regras do banco (no menu Authentication, item Policies; em versões antigas do painel, fica em Database). Ela mostra cada tabela e as regras de cada uma.

Passo 2. Pra cada tabela com dado de cliente, faça uma linha numa folha com quatro colunas: ler · criar · alterar · apagar. Em cada uma, escreva quem pode e qual linha. Exemplo: "ler: logado, só o dono".

Passo 3. Marque com ✗:

  • tabela sem regra nenhuma, ou com o selo vermelho Unrestricted (sem restrição);
  • qualquer condição que seja só true em tabela com dado de cliente;
  • regra de criar ou alterar sem with check;
  • anon ou public numa tabela que só quem fez login deveria ver;
  • ação em branco que o app usa (o app apaga, mas não há regra de apagar: alguém liberou de outro jeito).

Não entendeu uma regra? Cole na IA: "traduza esta regra numa frase: quem pode, fazer o quê, em qual linha. Não altere nada."

Passo 4. Pra cada ✗, um pedido de cada vez: "na tabela boletins, a leitura deve ser só do dono da linha, e criar ou alterar deve conferir que o dono gravado é quem está logado". Depois, leia a regra nova com a mesma ficha.

Passo 5. Ler não é provar. Abrir a tabela no painel não serve: lá você entra como dono do projeto e vê tudo. A prova é entrar como Ana e como Bruno no app: o teste das duas contas, seis linhas, dez minutos.

Onde isso se encaixa

Ler a regra é o N do ciclo que eu ensino: não aceitar sem prova. "Liguei o RLS" é relato; a ficha é leitura; as duas contas são prova.

E a regra por linha é uma parede de várias. Só vale se o banco for seu, se a chave secreta não estiver no navegador, se houver ponto de retorno quando a regra nova quebrar uma tela. Cada uma tem seu teste, e elas têm ordem.

Fontes

  • Supabase, Row Level Security (tabela sem RLS "readable and writable by any role with a grant on it"; RLS sem regra não entrega nada; exemplo de regra com auth.uid(); USING e WITH CHECK): https://supabase.com/docs/guides/database/postgres/row-level-security
  • A frase do início está numa lista de falhas comuns compartilhada por programadores brasileiros, registrada no swipe file da pesquisa deste blog.