Saltar para o conteúdo

Supabase raw_user_meta_data: como um registo virou Owner

Supabase

Ver como MarkdownAbrir em ClaudeAbrir em ChatGPTAbrir em Perplexity

A pergunta também é copiada. Se o chat abrir vazio, cole-a.

Em resumo

raw_user_meta_data guarda tudo o que o browser passou a supabase.auth.signUp(). Numa aplicação em produção, o trigger handle_new_user lia dali a função do novo utilizador, por isso qualquer pessoa podia registar-se com o id da função Owner e ficar dona da conta. A correção: a função vem de uma tabela em que só o servidor escreve, preenchida antes de o convite ser enviado.

Isto foi no registo de horas da empresa de auditoria, uma aplicação Supabase em que todas as regras de acesso vivem na base de dados. O bug estava no trigger que cria um perfil para cada novo utilizador.

O que fazia o trigger?

Simplificado, era assim:

create function public.handle_new_user()
returns trigger
language plpgsql
security definer set search_path = ''
as $$
declare
  _role_id uuid := (new.raw_user_meta_data ->> 'role_id')::uuid;
begin
  insert into public.profiles (id, full_name, role_id)
  values (new.id, new.raw_user_meta_data ->> 'full_name', _role_id);
  return new;
end;
$$;

O fluxo de convite colocava ali o role_id certo, por isso no uso normal funcionava. Mas raw_user_meta_data é o options.data de um registo, e é o browser que decide o que lá entra:

await supabase.auth.signUp({
  email: 'me@example.com',
  password: '...',
  options: { data: { role_id: '<owner role uuid>' } },
})

A verificação de permissões devolvia verdadeiro para os owners antes de olhar para qualquer outra coisa, por isso esse único campo dava controlo total da conta. O próprio guia de Users do Supabase diz que os metadados do utilizador não devem ser usados em políticas de RLS nem em lógica de autorização, porque o utilizador pode editá-los sem qualquer verificação.

De onde vinha a função de um novo utilizador. O trigger handle_new_user antes e depois da correção. Antes: quem se registasse: signUp options.data → raw_user_meta_data → handle_new_user → Função Owner; Depois: só quem foi convidado: Função de convite → pending_invites → handle_new_user → Função do convite.
Do trigger de registo de uma aplicação Supabase em produção, junho de 2026.

O que resolveu?

A função passou a vir de uma tabela privada em que o cliente não pode tocar:

create table private.pending_invites (
  email text primary key,
  role_id uuid,
  invited_by uuid,
  created_at timestamptz default now()
);
revoke all on table private.pending_invites from anon, authenticated;

A função de convite corre no servidor, com a permissão de quem convida verificada. Escreve primeiro o convite pendente e só depois cria a ligação do convite. O trigger procura a função pelo email e apaga a linha. O nome continua a vir dos metadados, o que não faz mal: são dados de apresentação.

A ordem importa. Um convite cria o utilizador de imediato: o guia de Users do Supabase indica que convidar um email novo cria um utilizador não confirmado. O trigger dispara nesse momento, por isso a linha pendente já tem de existir.

Como verificar a sua aplicação?

Abra a sua função handle_new_user, ou qualquer trigger em auth.users, e procure raw_user_meta_data. O nome e o avatar vindos dali não têm problema. Função, empresa, plano, tenant ou qualquer outra coisa que dê acesso tem de vir de uma tabela ou de raw_app_meta_data, que o utilizador não pode alterar.

Depois experimente: registe-se pela API com role_id ou role: 'admin' em options.data. A nova conta não deve receber nada a mais.

Este tipo de verificação é o primeiro passo do meu método de testes de RLS. Para aplicações multi-tenant, veja também o que o RLS não apanha num utilizador em duas orgs. As outras verificações que faço em todas as apps Supabase estão na minha lista de segurança para apps Lovable. A primeira que vejo no dashboard é que schemas a Data API expõe. A Auditoria de segurança Supabase cobre funções, grants, políticas e definições de auth com 45 regras do OWASP Top 10:2025, não o que um trigger copia dos metadados do registo, por isso esta continua a ser uma verificação manual.

© 2026 Denys Zaichenko. Artigo original: https://madsalt.tech/pt/notes/supabase-raw-user-meta-data-role/