Módulo Authorization — visão geral
O módulo de autorização implementa o RBAC (controle de acesso por papéis) do Portal 2.0: o
catálogo de permissões, os papéis (system roles canônicos e papéis customizados por tenant), a
atribuição de papéis a usuários em um tenant ou em uma unidade, políticas de negação/permissão
adicionais (ABAC), a consulta das próprias permissões efetivas e as rotas de inspeção/diagnóstico
usadas por suporte e automação. Ele não autentica ninguém — quem chega até este módulo já passou
pela autenticação (identity/authentication e identity/oauth, ver
Authentication) e traz um access token válido. O módulo de
autorização decide o que esse usuário pode fazer, e o AuthorizationGuard compartilhado
(@mobilemed/shared-module/iam) é o ponto único onde essa decisão é aplicada em toda rota
protegida do backend — não só nas rotas deste módulo.
Ele vive em um pacote do backend (NestJS): identity/authorization (core/http/persistence). O
guard e os modelos de permissão (AuthorizationGuard, Authorize, IamClaim, PermissionMask,
PermissionCatalog) vivem em shared/module/iam e são consumidos por este e por outros módulos
do backend (exame, laudo, empresa etc.) através do decorator @Authorize(...).
Fonte de verdade. Este levantamento foi feito inteiramente a partir do código atual (branch
integration/with-fixlaudo) e dos testes em__test__. Não existe rascunho anterior deste módulo. Os cartões do Bitrix (funil 562, F-001 a F-008 e F-S01) descrevem o sistema legado (mm-pacs-portal-api, Express, perfis por empresa em MongoDBUsuarioPerfil) e têm o campo "status vs. código real" vazio em todos os cartões consultados — foram usados só como contexto de intenção de produto (o modelo de "papel por empresa", a exigência de MFA para perfis privilegiados, a obrigação de trilha de auditoria), nunca como fonte do comportamento atual. O RBAC atual não é uma migração 1:1 do legado: trocou perfil numérico único por tenant/empresa por um bitmask de permissões por tenant e por unidade, e acrescentou um mecanismo de políticas (ABAC) que o legado não tinha.
Prefixo de rotas e versionamento
Como em authentication e user, a API usa versionamento por URI (VersioningType.URI, versão
padrão 1): toda rota deste módulo é servida sob /v1/... (ex.: /v1/authorization/permissions,
/v1/tenants/:tenantId/roles). Em desenvolvimento local, o serviço sobe na porta 3000
(MAIN_API_PORT, default 3000) e expõe o Swagger em http://localhost:3000/docs.
Modelo de permissões
- Catálogo de permissões: um conjunto fixo de permissões nomeadas (
PermissionName, ex.:exam:read,role:manage,policy:write), cada uma com umresource, umaactione uma posição de bit (bit_position) fixa e nunca reaproveitada — permissões descontinuadas ficam marcadas comoretiredno catálogo em vez de terem o bit reaproveitado.PermissionCatalog.all()inclui asretired;PermissionCatalog.assignable()as exclui — é essa segunda lista que pode ser atribuída a um papel ou usada em uma política. Ver Catálogo de permissões e papéis do sistema. - Papel (role): um conjunto de permissões, representado internamente como um bitmask
decimal (
permission_mask, inteiro grande serializado como string — soma de2^bit_positionde cada permissão do papel). Um papel ésystem(canônico, sem tenant, imutável via API) outenant_custom(criado por um tenant, mutável). Ver Papéis.- Nove papéis de sistema são semeados no boot da aplicação
(
BootstrapIdentityAuthorizationCatalogService→ApplyIdentitySystemRoleCatalogService), reconciliando o catálogo fixo em código com o banco:administrator,manager,doctor,technician,resident,read_only,typist,requestor,financial— cada um mapeado a umlegacy_profile_idnumérico (1 a 9) que corresponde ao perfil do sistema legado (PROPRIETARIO,GESTOR,MEDICO,TECNICO,RESIDENTE,SOMENTE_LEITURA,DIGITADOR,SOLICITANTE,FINANCEIRO, respectivamente, pelos cartões do Bitrix). - O papel
manageré o único "reconciliado" de forma especial: ele recebe todo o catálogo atribuível, excetoreport:sign— assinar um laudo é ato clínico, e um gestor administra a unidade sem necessariamente ser médico (comentário no código, decisão datada de 22/09/2026). O mesmo raciocínio removereport:signdo conjunto embutido deadministrator.
- Nove papéis de sistema são semeados no boot da aplicação
(
- Atribuição de papel a usuário: liga um
userIda umroleIdnum tenant (papel tenant-wide) ou numa unidade específica daquele tenant — nunca as duas coisas na mesma atribuição. TemgrantedBy,grantedAt, e opcionalmenteexpiresAt. Ver Atribuição de papéis. - Permissão efetiva: a união (OR bit a bit) dos bitmasks de todo papel ativo e não expirado
atribuído a um usuário num escopo. Um grant de tenant vale para todas as unidades daquele
tenant; um grant de unidade vale só naquela unidade. Isso é resolvido pelo
EffectivePermissionScopeResolver(shared/module/iam) a partir de uma claim cacheada por usuário (ver "Como o guard decide", abaixo). - Política (policy): uma regra adicional de
allow/denypor tenant, amarrada a uma permissão, com prioridade numérica, condições opcionais (comparação de atributos por caminhoa.b.c, com suporte a referenciar outro atributo via$a.b.c) e uma lista opcional de papéis- alvo (se vazia, aplica-se a qualquer papel). Políticas nunca ampliam o que o RBAC já concedeu — elas só podem negar uma permissão que o RBAC concederia, ou confirmar explicitamente uma permissão já concedida; não existe caminho de política que conceda uma permissão sem o bit correspondente no papel. Ver Políticas de autorização.
Como o guard decide (AuthorizationGuard)
Toda rota protegida (deste módulo ou de qualquer outro) declara sua exigência com o decorator
@Authorize({ permissions, match, scope }) e é protegida por IdentityOAuthAccessTokenGuard (que
autentica o access token) seguido de AuthorizationGuard (que autoriza). O guard:
- Lê os metadados de
@Authorizedo handler (ou lança erro 403AUTHORIZATION_METADATA_INVALIDse a rota não declarou nenhum — falha fechada por construção). - Carrega a claim de autorização do usuário (
AuthorizationClaimReader.findForUser) — um snapshot comisPlatformAdmin,roleIdse os bitmasks por tenant/unidade, versionado por um contador (authorization_version) que é incrementado a cada mutação relevante (papel atribuído/revogado, permissões de um papel alteradas etc.). A leitura tenta primeiro um cache distribuído (Redis) e cai para o Postgres se ele estiver indisponível ou desatualizado — confirmado em teste (identity-own-permissions.e2e.spec.ts: "continues serving least-privilege permissions from PostgreSQL when the distributed claim read fails"). Sem claim → 403AUTHORIZATION_CLAIM_UNAVAILABLE(não 404 — a rota não revela se o recurso existe). - Resolve o escopo da exigência (
tenant,unit,resource,current-user,global-catalog,tenant-list,unit-list,organization-listouplatform-admin) a partir de parâmetros da própria requisição (path/query/body) e verifica se o bitmask do usuário nesse escopo concede toda (match: 'all') ou alguma (match: 'any') das permissões exigidas. Sem concessão suficiente → 403ForbiddenActioncomrequiredPermissionno corpo do erro. - Se a permissão foi concedida pelo RBAC e o usuário não é
platformAdmin, avalia as políticas ativas daquele tenant que citam a mesma permissão contra o escopo resolvido. Uma políticadenyque combine (papel-alvo e condições) derruba uma concessão RBAC já validada → 403AUTHORIZATION_POLICY_DENIED.platformAdminpula essa etapa inteira. - Se tudo passou, popula
request.authorizationContext(usado pelos handlers para saber quais tenants/unidades o ator pode enxergar numa listagem) e deixa a requisição prosseguir.
Um usuário sem permissão nunca recebe 404 para esconder a existência de um recurso — o padrão
observado neste módulo é sempre 403 (às vezes com errorCode específico), com uma exceção:
platform-admin ou unit/tenant inexistentes/inativos também resultam em 403
AUTHORIZATION_SCOPE_UNAVAILABLE, não em 404. Isso é deliberado — ver
insufficientPermission/scopeUnavailable em AuthorizationGuard.
platformAdmin (IamClaim.isPlatformAdmin) contorna toda checagem de bitmask e de política — é a
única forma de bypass confirmada no código.
Relação com outros módulos de identidade
identity/user: dono do cadastro do usuário. A autorização não cria nem edita usuários — ela só lê (IdentityUserRepository.findActiveUserById) para confirmar que o usuário de uma atribuição existe e está ativo, e usa o diretório de usuários (FindIdentityUserDirectoryService) para montar a lista de "membros de unidade" (ver Membros de unidade).identity/session: a claim de autorização é resolvida poruserId, não por sessão — a sessão e o nível de confiança da autenticação (assurance:PASSWORD/MFA) são geridos poridentity/sessione checados independentemente pelo módulo de autenticação. Este módulo não eleva nem consultaassurance.shared/module/audit: toda mutação (criar/atualizar/retirar papel, políticas, atribuições) grava um evento no outbox de auditoria (IdentityOutboxEventEntity) com autor, IP,clientId, antes/depois — confirmado em cada*.service.tsde escrita deste módulo.shared/module/iam(o guard compartilhado): os módulos de exame, laudo, empresa etc. usam o mesmoAuthorizationGuarde o mesmo@Authorize(...)— este módulo é dono do catálogo e das atribuições, não do enforcement em si, que é transversal a todo o backend.
Rate limiting
Não foi identificado @Throttle/RateLimitGuard dedicado em nenhuma rota deste módulo neste
levantamento — diferente de authentication, que aplica rate limit a rotas sensíveis a força
bruta. As rotas de autorização são protegidas apenas pelo AuthorizationGuard (RBAC/ABAC).
Páginas deste módulo
| Página | Cobre |
|---|---|
| Catálogo de permissões e papéis do sistema | GET /authorization/permissions, /resources, /system-roles, /permission-bit-catalog |
| Papéis | CRUD de papéis customizados por tenant, clonagem e gestão de permissões de um papel — tenants/:tenantId/roles/* |
| Atribuição de papéis | Conceder, listar, atualizar expiração e revogar papéis de um usuário num tenant ou unidade; permissões efetivas |
| Minhas permissões | GET /me/permissions, GET /authorization/my-permissions, GET /authorization/my-permissions/changed |
| Políticas de autorização | CRUD de políticas allow/deny por tenant e avaliação pontual — tenants/:tenantId/policies/* |
| Inspeção de autorização | POST checks/bulk-checks, GET users/:userId/effective, GET consistency |
| Membros de unidade | GET units/:unitId/members — diretório de usuários com papel numa unidade |
| Exportação de permissões | GET units/:unitId/users/permissions-export — CSV de permissões efetivas |
:::tip OpenAPI
A documentação interativa (schemas + "Try it out") está disponível em /docs no ambiente onde a
API está rodando (local: http://localhost:3000/docs).
:::