Papéis — API
CRUD de papéis customizados por tenant (tenant_custom), incluindo clonagem de um papel visível
e gestão granular das permissões de um papel. Também expõe leitura dos papéis de sistema no mesmo
formato (system, imutáveis por esta rota).
Funcionamento
- Criar (
POST /tenants/:tenantId/roles): cria um papeltenant_customsem nenhuma permissão (permission_mask: "0"). O nome deve ser único entre os papéis não excluídos daquele tenant. - Clonar (
POST /tenants/:tenantId/roles/:roleId/clones): copia as permissões de um papel visível no tenant (de sistema outenant_customdo próprio tenant) para um novo papeltenant_custom, com nome/descrição novos. O ator precisa possuir, ele mesmo, toda permissão que está sendo clonada (ver RN-04); senão a clonagem é negada mesmo que ele tenharole:write. - Listar (
GET /tenants/:tenantId/roles) e buscar por lote (POST /tenants/:tenantId/roles/lookups): listam papéis visíveis no tenant (sistema +tenant_customdo tenant), com filtro por tipo, busca textual (nome/nome de exibição) e paginação. O lookup em lote aceita até 1000role_idse devolve só os que existem e são visíveis; lista vazia devolve[]sem consultar o banco. - Buscar um (
GET /tenants/:tenantId/roles/:roleId): 404 se o papel não existe, foi excluído, ou étenant_customde outro tenant — a visibilidade nunca revela a existência de um papel de outro tenant. - Atualizar (
PATCH /tenants/:tenantId/roles/:roleId) e retirar (DELETE .../roles/:roleId): exigem concorrência otimista —expected_versionno corpo (update) ou headerIf-Match(retire) casando com a versão atual do papel. Um papelsystemnunca pode ser alterado ou retirado por aqui (IDENTITY_SYSTEM_ROLE_IMMUTABLE). - Adicionar (
POST .../roles/:roleId/permissions), remover uma (DELETE .../roles/:roleId/permissions/:permissionId) e substituir todas (PUT .../roles/:roleId/permissions) as permissões de um papeltenant_custom: todas passam pelo mesmo caminho de escrita (ReplaceIdentityRolePermissionsService), incrementam a versão do papel e disparam o outbox de auditoria. Adicionar uma permissão já presente, ou remover uma ausente, é rejeitado como mutação sem efeito (IDENTITY_ROLE_PERMISSION_MUTATION_EMPTY).
Qualquer mutação de permissões de um papel incrementa a authorization_version de todo usuário
com atribuição ativa desse papel — na próxima leitura, a claim desses usuários é recalculada.
Endpoints
| Método | Rota | Descrição |
|---|---|---|
| POST | /v1/tenants/:tenantId/roles | Cria um papel customizado |
| POST | /v1/tenants/:tenantId/roles/:roleId/clones | Clona um papel visível para um novo papel customizado |
| POST | /v1/tenants/:tenantId/roles/lookups | Resolve papéis visíveis por uma lista de IDs |
| GET | /v1/tenants/:tenantId/roles | Lista papéis visíveis no tenant |
| GET | /v1/tenants/:tenantId/roles/:roleId | Lê um papel visível no tenant |
| PATCH | /v1/tenants/:tenantId/roles/:roleId | Atualiza nome/descrição de um papel customizado |
| DELETE | /v1/tenants/:tenantId/roles/:roleId | Retira (soft-delete) um papel customizado |
| POST | /v1/tenants/:tenantId/roles/:roleId/permissions | Adiciona permissões a um papel customizado |
| DELETE | /v1/tenants/:tenantId/roles/:roleId/permissions/:permissionId | Remove uma permissão de um papel customizado |
| PUT | /v1/tenants/:tenantId/roles/:roleId/permissions | Substitui todas as permissões de um papel customizado |
Versão: v1
Swagger: Identity — Roles · Rota (Dev): http://localhost:3000/v1/tenants/{tenantId}/roles
Permissões
| Rota | Guards | Permissão exigida (escopo tenant) |
|---|---|---|
POST /roles, PATCH /:roleId, DELETE /:roleId, POST /:roleId/clones | IdentityOAuthAccessTokenGuard, AuthorizationGuard | role:write |
GET /roles, GET /:roleId, POST /lookups | idem | role:read |
POST /:roleId/permissions, DELETE /:roleId/permissions/:permissionId, PUT /:roleId/permissions | idem | role:manage e permission:manage (match: all) |
Além da permissão RBAC da rota, toda mutação de papel/permissão passa por
IdentityRolePermissionAssignmentPolicy.assertActorCanAssignPermissionsInTenant (ou
assertActorCanAssignRole, no fluxo de atribuição — ver Atribuição de papéis):
o ator só pode conceder a um papel uma permissão que ele mesmo possui no tenant (bit a bit),
exceto se for platformAdmin. Isso impede escalonamento de privilégio mesmo por quem tem
role:manage.
Headers
| Header | Obrigatório | Descrição |
|---|---|---|
Authorization | Sim | Bearer <access_token> |
If-Match | Sim, em DELETE /:roleId | Versão atual do papel (inteiro ≥ 1); ausente/inválido → IDENTITY_ROLE_VERSION_REQUIRED |
Path parameters
| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
tenantId | uuid | Sim | Tenant dono do papel (ou tenant a partir do qual um papel system é visível) |
roleId | uuid | Sim, nas rotas de item | Papel alvo |
permissionId | uuid | Sim, em DELETE .../permissions/:permissionId | Permissão a remover |
Query parameters
GET /roles:
| Nome | Tipo | Obrigatório | Default | Descrição |
|---|---|---|---|---|
page | integer | Não | 1 | Página (mínimo 1) |
limit | integer | Não | 20 | Itens por página (1 a 100) |
search | string | Não | — | Busca por nome ou nome de exibição (até 128 caracteres) |
include_system | boolean | Não | true | Inclui papéis de sistema na listagem |
role_ids | uuid[] | Não | — | Restringe a listagem a estes IDs |
type | system | tenant_custom | Não | — | Filtra por tipo de papel |
Body
POST /roles e PATCH /:roleId — MaintainIdentityRoleRequest:
json{ "name": "radiology-reviewer", "display_name": "Radiology reviewer", "description": "Reviews radiology exams" }
| Campo | Tipo | Obrigatório | Validação |
|---|---|---|---|
name | string | Sim | até 128 caracteres, não vazio |
display_name | string | Sim | até 128 caracteres, não vazio |
description | string | Não | até 500 caracteres |
expected_version | integer | Sim, só em PATCH | inteiro ≥ 1 |
POST /:roleId/permissions — AddIdentityRolePermissionsRequest:
json{ "expected_version": 3, "permission_ids": ["b7c5...", "a91f..."] }
| Campo | Tipo | Obrigatório | Validação |
|---|---|---|---|
expected_version | integer | Sim | inteiro ≥ 1 |
permission_ids | uuid[] | Sim | 1 a 100 itens, sem duplicatas; cada um deve existir no catálogo (senão IDENTITY_PERMISSION_NOT_FOUND) |
PUT /:roleId/permissions — ReplaceIdentityRolePermissionsRequest:
json{ "expected_version": 3, "permissions": ["exam:read", "report:read"] }
| Campo | Tipo | Obrigatório | Validação |
|---|---|---|---|
expected_version | integer | Sim | inteiro ≥ 1 |
permissions | string[] | Sim | sem duplicatas; cada nome deve pertencer ao catálogo atribuível (IsIn sobre PermissionCatalog.assignable()) |
POST /lookups — IdentityRoleLookupRequest:
json{ "role_ids": ["b7c5...", "a91f..."] }
| Campo | Tipo | Obrigatório | Validação |
|---|---|---|---|
role_ids | uuid[] | Sim | até 1000 itens; acima disso → IDENTITY_ROLE_LOOKUP_LIMIT_INVALID (400) |
Response
2xx — IdentityRoleResponse:
json{"data": {"id": "b7c5...uuid","description": "Reviews radiology exams","name": "radiology-reviewer","display_name": "Radiology reviewer","type": "tenant_custom","tenant_id": "1f2a...uuid","permission_mask": "17","source_role_id": null,"permissions": ["exam:read", "report:read"],"permission_count": 2,"permission_details": [ { "name": "exam:read", "resource": "exam", "action": "read", "bit_position": 0, "description": "Read exams" } ],"is_active": true,"created_at": "2026-09-01T12:00:00.000Z","updated_at": "2026-09-01T12:00:00.000Z","version": 1}}
Mapa de campos
| Campo | Tipo | Valores possíveis | Default | Descrição |
|---|---|---|---|---|
type | string | system, tenant_custom | — | system se tenant_id não foi informado na criação (só ocorre no seed do catálogo) |
tenant_id | uuid | null | — | null para papéis system | Tenant dono do papel customizado |
permission_mask | string (bigint) | soma de 2^bit_position das permissões | "0" na criação | Bitmask decimal persistido |
permissions / permission_details | string[] / object[] | nomes do catálogo atribuível | [] na criação | Permissões do papel, decodificadas do bitmask |
source_role_id | uuid | null | — | null | Papel de origem, quando este papel foi criado por clonagem |
version | integer | ≥ 1 | 1 | Usado para concorrência otimista em PATCH/DELETE/mutações de permissão |
Erros
| Classe de erro | errorCode | Status | Quando ocorre |
|---|---|---|---|
| (validação de payload/UUID) | BAD_REQUEST / INVALID_UUID | 400 | corpo, tenantId/roleId malformados |
| — | IDENTITY_ROLE_LOOKUP_LIMIT_INVALID | 400 | role_ids do lookup acima de 1000 itens |
| — | IDENTITY_ROLE_PERMISSION_MUTATION_EMPTY | 400 | adicionar/remover uma permissão sem mudar o conjunto final |
| — | IDENTITY_PERMISSION_NOT_FOUND | 400 | permission_ids de POST .../permissions não existe no catálogo |
| — | IDENTITY_ROLE_VERSION_REQUIRED | 400 | DELETE :roleId sem If-Match válido |
UnauthenticatedError | IDENTITY_INVALID_OAUTH_ACCESS_TOKEN | 401 | access token ausente/inválido |
ForbiddenAction | IDENTITY_SYSTEM_ROLE_IMMUTABLE | 403 | tentativa de alterar/retirar/remover permissões de um papel system |
ForbiddenAction | IDENTITY_ROLE_PERMISSION_ESCALATION_FORBIDDEN | 403 | ator tenta conceder/clonar uma permissão que ele mesmo não possui no tenant |
ForbiddenAction (guard) | — | 403 | ator sem role:write/role:read/role:manage+permission:manage no tenant |
IdentityRoleNotFoundError | IDENTITY_ROLE_NOT_FOUND | 404 | papel inexistente, excluído, ou não visível no tenant informado |
AlreadyExistsError | IDENTITY_ROLE_NAME_ALREADY_EXISTS | 409 | outro papel não excluído já usa este name no mesmo escopo (tenant ou global) |
IdentityRoleVersionConflictError | IDENTITY_ROLE_VERSION_CONFLICT | 409 | expected_version/If-Match não bate com a versão atual |
Regras de negócio
| ID | Regra | Comportamento esperado |
|---|---|---|
| RN-01 | Papel system é imutável por esta API | PATCH, DELETE e as três rotas de permissões de papel sempre negam com IDENTITY_SYSTEM_ROLE_IMMUTABLE para type: system |
| RN-02 | Nome de papel é único por escopo (tenant, ou global entre os system) | criar/clonar/atualizar com nome já em uso → 409 IDENTITY_ROLE_NAME_ALREADY_EXISTS |
| RN-03 | Toda mutação de papel usa concorrência otimista | expected_version (body) ou If-Match (header) deve casar com version atual, senão 409 |
| RN-04 | Sem escalonamento de privilégio: o ator só concede o que já possui | adicionar/substituir/clonar permissões exige que o ator tenha, ele mesmo, cada permissão envolvida no tenant — platformAdmin está isento |
| RN-05 | Mutação de permissão precisa ter efeito | adicionar uma permissão já presente ou remover uma ausente é rejeitado (IDENTITY_ROLE_PERMISSION_MUTATION_EMPTY) |
| RN-06 | Mudança de permissões de um papel invalida a claim de todo usuário atribuído a ele | authorization_version incrementado para cada usuário com atribuição ativa daquele papel |
| RN-07 | Lookup em lote é limitado e idempotente | até 1000 role_ids; lista vazia não consulta o banco; IDs duplicados são deduplicados e ordenados |
Compliance
| Órgão / norma | Exigência | Como a rota atende |
|---|---|---|
| LGPD / HIPAA | Least privilege / não escalonamento | RN-04 impede que um administrador de papéis conceda permissão que não possui |
| HIPAA / ANVISA | Trilha de mudança de controle de acesso | toda criação/atualização/retirada/clonagem/mutação de permissão grava um evento no outbox de auditoria com autor, IP e antes/depois |
Variáveis de ambiente
Nenhuma variável de ambiente é consumida diretamente por estas rotas.
Tempo médio de resposta
A confirmar — responsável: time de Identity; data: 24/09/2026. Sem medição publicada.
Requisitos não funcionais
| Requisito | Definição |
|---|---|
| Idempotência | Não para POST (cada chamada tenta criar um novo papel); mutações de permissão via PUT/add/remove são idempotentes no resultado final, mas rejeitam uma chamada repetida sem efeito |
| Paginação | Sim, em GET /roles (limit 1–100, default 20) |
| Rate limit | Não identificado |
| Cache | Não identificado |
| Auditoria | Sim — todo evento de escrita grava no outbox (identity.role.created/updated/retired/cloned/permissions_replaced) |
Relacionado
- 📂 Módulo: Authorization
- 🔁 Depende de: Catálogo de permissões e papéis do sistema (fonte das permissões atribuíveis)
- 🔁 Próximo passo: Atribuição de papéis (conceder um papel a um usuário)