Pé no Chão #01: Saindo do Loop Infinito de RLS no Supabase
Você conhece aquela sensação. O projeto no Next.js está voando, a estrutura no Supabase parece sólida e você finalmente decide dar aquele passo responsável: configurar o Row Level Security (RLS) para proteger os dados.
Você escreve a política, clica em salvar, atualiza a página e... BUM, o Demogorgon, e nada carrega.
O console explode. E lá está a mensagem que faz qualquer desenvolvedor questionar suas escolhas de carreira: infinite recursion detected in policy.
O que diabos aconteceu?
Basicamente, você criou um "paradoxo tico-tico no fubá" (é o que me disseram). Você disse ao banco: "Para ler esta tabela, verifique se o usuário é um Admin nesta mesma tabela". O banco de dados, sendo uma máquina obediente, entra em um loop eterno tentando verificar a permissão de uma permissão que ainda não foi permitida.
Neste primeiro post da série Pé no Chão, vamos parar de brigar com o PostgreSQL e entender como usar SECURITY DEFINER e funções auxiliares para quebrar esse ciclo de uma vez por todas. Sem teorias complexas demais, apenas o código que você precisa para voltar a buildar sua aplicação ainda hoje.
O Código que quebra (O culpado)
Geralmente, o erro acontece quando tentamos ser diretos demais. Imagine que você tem uma tabela profiles e quer que apenas administradores leiam todos os dados. Você escreve algo assim:
-- NÃO FAÇA ISSO!
CREATE POLICY "Admins veem tudo" ON public.profiles
FOR SELECT USING (
(SELECT is_admin FROM public.profiles WHERE id = auth.uid()) = true
);O log do erro: infinite recursion detected in policy.
A lógica do erro: para verificar o is_admin, o Postgres precisa ler a tabela profiles. Para ler a tabela profiles, ele precisa rodar a política... que pede para ler a tabela profiles. É um loop sem fim.
A solução: quebrando o ciclo com SECURITY DEFINER.
Para resolver isso, precisamos de um "atalho" que verifique a permissão do usuário sem disparar novamente a política de segurança da tabela. A forma mais elegante de fazer isso no Supabase é criando uma função auxiliar com a propriedade SECURITY DEFINER.
1. Criando a função de verificação
Esta função rodará com os privilégios do criador (o dono do banco), ignorando as políticas de RLS apenas dentro do escopo dela.
CREATE OR REPLACE FUNCTION public.check_is_admin()
RETURNS boolean AS $$
BEGIN
RETURN EXISTS (
SELECT 1
FROM public.profiles
WHERE id = auth.uid()
AND is_admin = true
);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;2. Aplicando as políticas corretas
Agora, em vez de fazer um SELECT direto na política, chamamos a nossa nova função.
Dica de mestre: sempre use DROP POLICY antes para garantir que você está limpando as regras antigas.
Por que isso funciona?
Ao usar SECURITY DEFINER, a função check_is_admin() tem permissão para olhar a tabela profiles "por trás das cortinas". O Postgres não precisa validar o RLS para a função, o que interrompe o loop infinito e entrega o resultado (verdadeiro ou falso) instantaneamente para a política.
Dica de Performance (O pulo do gato)
Embora a função com SECURITY DEFINER resolva o erro, se o seu e-commerce escalar muito, chamar uma função no banco de dados para cada linha pode ter um custo.
A alternativa pro: se a informação de "admin" não mudar o tempo todo, você pode incluir essa claim diretamente no JWT do usuário. Assim, sua política de RLS fica ainda mais rápida:
-- Exemplo usando metadados do JWT (sem tocar no banco)
CREATE POLICY "Admin ultra rápido" ON public.profiles
FOR SELECT USING (
(auth.jwt() ->> 'is_admin')::boolean = true
);Conclusão:
Resolver o erro de recursão infinita é um rito de passagem para quem domina o Supabase. Entender como o PostgreSQL lida com o contexto de segurança permite que você crie sistemas não apenas seguros, mas extremamente rápidos.
Além de resolver o erro, essa abordagem é mais performática. Em vez de o banco tentar resolver sub-consultas complexas em cada linha, ele apenas executa uma função rápida e otimizada.
O segredo do "Pé no Chão" é exatamente esse: entender onde o loop começa para saber onde cortá-lo. Agora que o seu banco de dados parou de "dar voltas", você está livre para focar no que realmente importa: a experiência do seu cliente.
Comentários
Carregando comentários...