Pular para o conteúdo

ADR-0033 Autorização

Padrão da empresa para o que um utilizador autenticado pode fazer — o controlo de acesso do Better Auth para papéis e permissões, verificado no servidor em todos os pontos de entrada, com o âmbito das linhas na query.

Decisão
Controlo de acesso do Better AuthGuardar e proteger
Estado
Aceite
Data
Âmbito
Predefinição da empresa para todas as novas apps

O ADR-0009 resolve quem é o utilizador. Não resolve o que ele pode fazer, pelo que cada produto inventa as suas verificações de papéis.

  • Dois tipos de ponto de entrada: server functions e rotas da API (ADR-0036). Uma verificação que só existe na UI não é uma verificação
  • Dois tipos de produto: single-tenant (o backoffice de uma empresa) e multi-tenant (clientes em organizações)
  • Duas perguntas por pedido: se este utilizador pode fazer este tipo de coisa, e se esta linha em particular é dele para mexer. A segunda responde-se no Postgres (ADR-0008)
  • O Better Auth já traz controlo de acesso nos plugins admin e organization, e os plugins são opt-in por produto

O que está em aberto: onde se definem os papéis e as permissões, onde se verificam e como se limita o âmbito das linhas.

Prós

  • Já está instalado; os papéis vivem nas nossas tabelas (user.role, ou a pertença à organização)
  • Uma só declaração tipada de recursos e ações; os papéis constroem-se a partir dela, e o servidor e o cliente partilham a definição
  • Uma API de servidor para verificar uma permissão e uma verificação síncrona no cliente para mostrar ou esconder controlos

Contras

  • Baseado em papéis: partilhar um registo com uma pessoa não está modelado
  • Dois plugins com duas chamadas de verificação (userHasPermission, hasPermission)
  • Os papéis são código: mudar um é um deploy

Prós: as regras podem depender de atributos do registo; corre no servidor e no cliente. Contras: uma segunda definição de papéis ao lado da do Better Auth, e código de cola nosso para construir as permissões por pedido.

Prós: a base de dados impõe o âmbito mesmo quando uma query se esquece do where. Contras: cada pedido tem de definir a sua identidade dentro de uma transação numa ligação do pool; as políticas vivem nas migrações, longe do código que depende delas; mais difícil de testar e de depurar.

Prós: partilha fina, baseada em relações; um só modelo para todos os produtos. Contras: mais um serviço para operar e uma chamada de rede por verificação — muito mais do que as verificações de papéis precisam.

O controlo de acesso do Better Auth define os papéis e as permissões. O servidor verifica-os em todos os pontos de entrada, e a query limita as linhas.

Em concreto:

  • Um só módulo de permissões, src/lib/permissions.ts: uma declaração de recursos e ações (as const), passada ao createAccessControl de better-auth/plugins/access, e os papéis construídos a partir dela. É seguro para o cliente, pelo que fica em lib/ (ADR-0026); o plugin de servidor em server/auth.ts e o plugin de cliente importam o mesmo módulo
  • Um plugin por produto: admin para apps single-tenant (o papel está no utilizador), organization para apps multi-tenant (o papel está na pertença). As suas tabelas são geradas para o nosso schema Drizzle e migradas pelo Drizzle Kit, como no ADR-0009
  • O código pergunta por permissões, nunca por papéis: “este utilizador pode atualizar um projeto”, não “este utilizador é admin”. Acrescentar ou mudar um papel mexe então num só ficheiro
  • Todas as server functions e todos os handlers de rotas da API autorizam antes de chamar a função da funcionalidade (ADR-0027), através de um só helper que envolve auth.api.userHasPermission ou auth.api.hasPermission e recusa o pedido
  • O âmbito das linhas é um where. A permissão diz “pode atualizar projetos”; a query diz “este projeto, nesta organização”. As funções das funcionalidades recebem o ID do utilizador ou da organização a partir da sessão e põem-no na query Drizzle — nunca um ID enviado pelo cliente
  • Respostas: 401 quando não há sessão, 403 quando falta a permissão, 404 quando a linha está fora do âmbito do utilizador, para não revelar que existe
  • A verificação no cliente é cosmética: o checkRolePermission esconde ou desativa controlos; quem decide continua a ser o servidor
  • Testes: cada ponto de entrada protegido tem testes de guarda (ADR-0014) — um utilizador sem a permissão é recusado, e a linha de outro tenant não é encontrada
  • Não é a predefinição: row-level security, uma biblioteca de políticas ou um serviço de autorização. Um produto que precise de partilha por registo escreve o seu próprio ADR

Os fatores decisivos: nada de novo para instalar, papéis guardados nas nossas tabelas, uma só definição tipada partilhada pelo servidor e pelo cliente, e verificações que ficam nos handlers que já mantemos finos. Aceitamos que o âmbito das linhas dependa de todas as queries levarem o seu where.

Mais fácil:

  • Os papéis, as permissões e o seu significado estão num só ficheiro revisto por produto
  • Um papel novo é uma alteração a esse ficheiro, não uma procura de nomes de papéis pelo código
  • As falhas de autorização têm o mesmo aspeto em todos os pontos de entrada

Mais difícil:

  • Um where esquecido é uma fuga de dados que a base de dados não trava; a defesa são os testes de guarda e a revisão
  • O plugin admin traz também endpoints de ban e de impersonation — decidir que papéis os têm
  • Mudar um papel exige um deploy, a não ser que um produto ligue os papéis dinâmicos do plugin organization

Do que abdicamos: de regras baseadas em atributos e do isolamento imposto pela base de dados. Aceite — verificações de papéis mais queries com âmbito cobrem os nossos produtos, e um produto que os ultrapasse pode acrescentar qualquer um dos dois sem substituir isto.