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
Ligações
Contexto
Seção intitulada “Contexto”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
admineorganization, 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.
Opções consideradas
Seção intitulada “Opções consideradas”1. Controlo de acesso do Better Auth
Seção intitulada “1. Controlo de acesso do Better Auth”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
2. Uma biblioteca de políticas (CASL)
Seção intitulada “2. Uma biblioteca de políticas (CASL)”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.
3. Row-level security do Postgres
Seção intitulada “3. Row-level security do Postgres”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.
4. Um serviço de autorização (OpenFGA, Cerbos)
Seção intitulada “4. Um serviço de autorização (OpenFGA, Cerbos)”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.
Decisão
Seção intitulada “Decisão”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 aocreateAccessControldebetter-auth/plugins/access, e os papéis construídos a partir dela. É seguro para o cliente, pelo que fica emlib/(ADR-0026); o plugin de servidor emserver/auth.tse o plugin de cliente importam o mesmo módulo - Um plugin por produto:
adminpara apps single-tenant (o papel está no utilizador),organizationpara 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.userHasPermissionouauth.api.hasPermissione 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:
401quando não há sessão,403quando falta a permissão,404quando a linha está fora do âmbito do utilizador, para não revelar que existe - A verificação no cliente é cosmética: o
checkRolePermissionesconde 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.
Consequências
Seção intitulada “Consequências”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
whereesquecido é 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
admintraz 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.

