Ir para o conteúdo

Guias de engenharia

Importações no Supabase, RLS e campos controlados pelo tenant

Separe os valores do CSV da autoridade sobre o tenant. Entenda JWTs de usuário, acesso com service role e a limitação do COPY do PostgreSQL quando a RLS se aplica.

Mohamed Atef · ImportFlow
Última revisão

Obtenha o tenant da importação a partir de uma decisão autenticada e autorizada da aplicação. Exclua-o do mapeamento de colunas do cliente. Depois, imponha a gravação permitida no destino com o papel que realmente a executará.

Habilitar a RLS é apenas parte desse projeto. Uma requisição à API do Supabase com um JWT de usuário, uma requisição do servidor usando autoridade de service role e uma sessão psql do proprietário do banco não têm as mesmas permissões. Uma importação que funciona no painel não demonstra que funcionará pela sessão de um cliente nem que o isolamento entre tenants foi exercitado.

Se você precisa apenas das etapas de upload, comece por importação de CSV para o Supabase. Esta página trata da fronteira de autoridade por trás dessas gravações.

Escopo e premissas. Comportamento do PostgreSQL 17 e do Supabase Auth/Data API. Os exemplos de políticas são ilustrativos e exigem o modelo de associação e concessão de privilégios descrito.

Identifique primeiro o caminho de execução

CaminhoAutoridade a inspecionarO que uma gravação bem-sucedida comprova
Data API com um JWT de usuário autenticadoPapel autenticado efetivo, declarações do JWT, privilégios concedidos e políticasA requisição passou pelas verificações aplicáveis naquele momento
Backend usando autorização de service rolePapel privilegiado e a autorização do próprio backendNão deduza que a RLS foi aplicada; service_role tem BYPASSRLS
Painel / conexão privilegiada com o banco de dadosPapel real da sessão, propriedade e atributos que permitem ignorar a RLSNão é evidência de acesso de um usuário comum da aplicação
Login de banco de dados com escopo restrito e controlado pelo clientePrivilégios do papel, propriedade da tabela, RLS aplicável e contexto da sessãoSomente as permissões e verificações exercitadas por esse login

O guia de chaves de API do Supabase distingue o acesso publishable/anon do acesso secret/service-role. Seu guia de RLS também explica um detalhe importante do cliente: um cliente inicializado com uma chave de serviço pode seguir o contexto de RLS de um usuário autenticado quando sua autorização é substituída pela sessão desse usuário. Inspecione a credencial efetiva de Authorization, não apenas a chave usada na criação do cliente.

No PostgreSQL, superusuários e papéis com BYPASSRLS ignoram a segurança de linha. Proprietários de tabelas normalmente também a ignoram, a menos que FORCE ROW LEVEL SECURITY se aplique ao proprietário. FORCE não restringe um superusuário nem um papel com BYPASSRLS. Por isso, testar apenas como proprietário da tabela é insuficiente.

Privilégios concedidos e RLS resolvem problemas diferentes. Um papel precisa do privilégio relevante de tabela ou coluna; sua política de linha também deve permitir a operação quando a RLS se aplica. Se nenhuma política aplicável permitir o acesso, uma tabela com RLS habilitada usa negação por padrão para papéis comuns. Veja as regras de segurança de linha do PostgreSQL.

Vincule uma importação a um tenant autorizado

Considere um fundador importando contatos para um workspace. Seu CSV contém account_id de um CRM anterior. Seu banco de dados também tem um identificador de conta. O valor antigo pode ser útil como referência externa, mas não é evidência de que o importador possa gravar no workspace correspondente do seu sistema.

A aplicação deve autenticar o operador, verificar sua permissão de importação para o workspace selecionado e vincular esse workspace à tarefa de importação. O mapeamento de linhas aprovado passa então a conter apenas campos como e-mail e nome de exibição. O componente de gravação fornece o contexto de tenant de forma independente.

Pseudocódigo ilustrativo do servidor
subject = authenticate(request)
tenant = authorizeImport(subject, selectedWorkspace)
job = createJob({ tenant, actor: subject.id, contractVersion })

for each submitted row:
  reject keys outside the approved source fields
  validate source values again on the server
  write approved values with tenant = job.tenant

Autenticação por si só não é autorização. Um usuário pode estar autenticado sem pertencer ao workspace selecionado ou pertencer a ele sem ter permissão para importar. Verifique a permissão para esta operação e defina como a revogação afeta tarefas na fila. Uma tarefa de longa duração não deve ganhar silenciosamente autoridade por tempo indeterminado a partir de uma sessão que era válida ontem.

Não use metadados editáveis pelo usuário como fonte de autoridade sobre o tenant. Se usar declarações de JWT, estabeleça quem as emite, quem pode alterá-las e como associações desatualizadas são tratadas. Uma declaração assinada só é tão útil quanto o processo confiável que a criou.

Use WITH CHECK para restringir novas linhas

A política a seguir ilustra uma aplicação em que uma tabela de associações confiável registra quem pode importar para cada tenant. Pressuponha que public.contacts(tenant_id uuid, email text) exista com ambas as colunas NOT NULL. Pressuponha que public.tenant_memberships(tenant_id uuid, user_id uuid, can_import boolean) esteja protegida contra concessões criadas pelo usuário e que suas permissões e políticas de SELECT permitam que um usuário leia suas próprias associações.

SQL
ALTER TABLE public.contacts ENABLE ROW LEVEL SECURITY;

CREATE POLICY contacts_member_import
ON public.contacts
FOR INSERT TO authenticated
WITH CHECK (
  EXISTS (
    SELECT 1
    FROM public.tenant_memberships AS membership
    WHERE membership.tenant_id = contacts.tenant_id
      AND membership.user_id = (SELECT auth.uid())
      AND membership.can_import
  )
);

Este é um fragmento de política, não uma instalação completa. O papel também precisa de privilégios INSERT adequados. Se a requisição retornar linhas, revise os privilégios e as políticas SELECT necessários para essa resposta. Uma política de INSERT usa WITH CHECK para testar a linha proposta; uma política de SELECT não a substitui.

Revise todas as políticas existentes. Políticas permissivas geralmente se combinam com OR, portanto adicionar esta política ao lado de uma política permissiva ampla de INSERT pode não restringir o acesso. Políticas restritivas se combinam de outra forma e precisam de uma política permissiva aplicável. Avalie o conjunto completo de políticas em vez de tratar uma expressão aparentemente adequada como a fronteira inteira.

Essa verificação de associação permite um tenant autorizado; ela não vincula uma requisição a um único tenant selecionado anteriormente para a tarefa. O componente de gravação ainda precisa rejeitar campos de tenant controlados pelo arquivo e usar o valor autorizado da tarefa. Se o modelo de ameaças incluir contornar esse componente, projete também uma fronteira de tarefa imposta no destino. Não apresente este fragmento como se fornecesse essa propriedade mais forte.

COPY não é um atalho para importar com a RLS do usuário

O PostgreSQL 17 não oferece suporte a COPY FROM quando a RLS se aplica ao papel executor. A documentação recomenda instruções INSERT equivalentes. Carregar uma tabela temporária de staging e depois usar um INSERT autorizado no destino pode ser um projeto adequado, desde que a sessão, os valores das linhas, os privilégios concedidos e as políticas sejam revisados.

Um proprietário de tabela ou papel que ignora a RLS pode se comportar de outra forma porque a segurança de linha pode não se aplicar a esse papel. Isso não é uma alternativa que preserve a garantia original das políticas de usuário. É um caminho de migração privilegiado cujas verificações de tenant e revisão operacional precisam ser estabelecidas separadamente.

Conexões SQL diretas também não carregam automaticamente o contexto JWT de um usuário do Supabase. Uma função como auth.uid() depende da disponibilidade das declarações da requisição ou sessão pelo caminho relevante. Não pressuponha que um login no psql represente o usuário final só porque se conecta ao mesmo banco de dados.

Para uma migração pontual operada pelo comprador, a execução privilegiada pode ser uma escolha deliberada. Peça ao engenheiro do comprador que revise a atribuição da origem ao tenant, as colunas exatas, o destino e as linhas resultantes. Mantenha esse procedimento distinto de um importador de usuário final controlado por RLS. A documentação do COPY descreve a limitação do banco de dados.

Teste as gravações proibidas, além da bem-sucedida

Use tenants de teste isolados e linhas sintéticas. Execute os testes com o mesmo papel e caminho de requisição do importador real, com os mesmos privilégios concedidos e o conjunto completo de políticas.

  • Um usuário com permissão de importação para o tenant A consegue criar uma linha aprovada em A.
  • Um usuário com permissão de importação apenas para o tenant A é rejeitado ao solicitar B.
  • Para uma tarefa já vinculada a A, um tenant B fornecido é rejeitado mesmo que o usuário tenha, separadamente, permissão para B. Isso exige controle no nível da tarefa além da política de associação.
  • Um membro sem permissão de importação é rejeitado.
  • Uma sessão não autenticada ou expirada não obtém um caminho alternativo de gravação privilegiada.
  • Um campo de tenant fornecido é rejeitado mesmo que coincida com o tenant correto.
  • Uma tarefa na fila segue a regra documentada após a revogação da associação.

Após o sucesso, leia a linha pelo caminho normal da aplicação para A e B. Confirme a visibilidade além da inserção. Evite que erros revelem identificadores ou dados de outro tenant. Se um teste só funciona como proprietário, você testou a autoridade do proprietário, não o caminho pretendido para o cliente.

Uma chave estrangeira de um contato para uma empresa também merece revisão: um ID de empresa globalmente válido pode pertencer a outro tenant. Use uma restrição de referência e uma estratégia de resolução adequadas ao escopo do tenant. O guia de chaves estrangeiras desenvolve esse exemplo.

Como isso se relaciona com o ImportFlow

O ImportFlow mantém a autorização da aplicação separada dos valores de tenant fornecidos pela planilha. Veja a arquitetura atual e os limites da implementação para a divisão de responsabilidades no nível do produto.

A aplicação continua responsável pela autoridade do usuário e do tenant. O ImportFlow trata valores protegidos de tenant ou de sistema como contexto da aplicação, em vez de campos de planilha editáveis pelo cliente, e a RLS sozinha não estabelece essa autoridade.

Fontes e leituras complementares