Skip to main content

Consultar logs de auditoria — API

Lista o histórico de eventos de auditoria já persistidos, com filtros e paginação, aplicando mascaramento de campos sensíveis na resposta. Existem três rotas equivalentes que diferem apenas em qual escopo organizacional restringe a consulta: todos os tenants que o chamador pode acessar, um tenant específico, ou uma unidade específica.

Funcionamento​

  1. JwtAuthenticationGuard exige um access token válido; AuthorizationGuard exige a permissão audit:read e resolve o escopo da rota (ver Permissões).
  2. A query string é validada (ListAuditLogsQueryDto): paginação, filtro por evento, por userId, por intervalo from/to, e por par resourceType+resourceId (os dois são obrigatórios juntos).
  3. Se o chamador não tem nenhum tenant acessível, a resposta é uma página vazia (não é erro).
  4. Se o filtro pedir um resourceType que o escopo da rota não permite (user nas rotas de tenant/unidade), a resposta também é uma página vazia.
  5. O repositório busca a página no Postgres, já filtrada por escopo, evento, usuário, período e recurso, ordenada por occurredAt DESC, createdAt DESC, seq DESC (mais recente primeiro; em empate, desempata pela ordem real de inserção na cadeia).
  6. Se a página tem itens, o conjunto de regras de mascaramento vigente (cacheado) é aplicado sobre metadata, before, after, description, ip, userId, targetUserId, clientId e resourceId de cada entrada, antes de montar a resposta.

Endpoints​

MétodoRotaDescrição
GET/v1/audit-logsHistórico entre todos os tenants que o chamador pode acessar
GET/v1/tenants/:tenantId/audit-logsHistórico restrito a um tenant (e suas unidades)
GET/v1/units/:unitId/audit-logsHistórico restrito a uma unidade

Versão: v1

Swagger: Audit · Rota (Dev): http://localhost:3001/v1/audit-logs

Lógica de decisão (comum às três rotas; o que muda é apenas a origem do escopo):

Permissões​

Todas as rotas exigem JwtAuthenticationGuard + AuthorizationGuard com a permissão audit:read (PermissionName.AUDIT_READ). O que muda entre as três é o escopo exigido:

RotaGuardsEscopo (Authorize)Efeito
GET /audit-logsJwtAuthenticationGuard, AuthorizationGuardtenant-list (filtro policy)Vê o histórico de todos os tenants que a política de autorização concede ao ator; sem nenhum tenant concedido, a lista vem vazia. Um administrador de plataforma vê tudo, incluindo o escopo __system__.
GET /tenants/:tenantId/audit-logsidemtenant, identificador = tenantId do pathExige que o tenantId do path esteja entre os tenants concedidos ao ator; caso contrário, 403.
GET /units/:unitId/audit-logsidemunit, identificador = unitId do pathExige que o unitId do path esteja entre as unidades concedidas ao ator; caso contrário, 403.

As rotas de tenant e unit excluem sempre entradas com resourceType: user, mesmo que o chamador tenha a permissão e peça esse filtro explicitamente (retorna página vazia). Ações sobre usuário só aparecem pela rota geral.

Headers​

HeaderObrigatórioDescrição
AuthorizationSimBearer <access_token>

Path parameters​

NomeTipoObrigatórioDescrição
tenantIdUUIDApenas na rota de tenantValidado por ParseUUIDPipe; UUID inválido → 400
unitIdUUIDApenas na rota de unidadeValidado por ParseUUIDPipe; UUID inválido → 400

Query parameters​

NomeTipoObrigatórioDefaultDescrição
pageintegerNão1Página (1-based)
limitintegerNão20Itens por página (máx. 100)
eventstringNão—Filtra por nome exato do evento (ex.: tenant.created); vazio/só espaços é tratado como ausente
userIdUUIDNão—Filtra pelo ator (sub do JWT)
fromISO 8601 date-timeNão—Limite inferior (inclusive) de occurredAt
toISO 8601 date-timeNão—Limite superior (inclusive) de occurredAt; deve ser ≥ from
resourceTypeenum AuditResourceTypeSó junto com resourceId—Tipo do recurso (ex.: exam, unit, user)
resourceIdstring (até 64)Só junto com resourceType—Identificador do recurso

resourceType e resourceId devem ser enviados juntos; enviar só um dos dois é 400.

Body​

Não se aplica — as três rotas são GET.

Response​

200 — PaginatedAuditLogsResponseDto:

json
{
"data": [
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"event": "tenant.created",
"description": "Tenant created",
"userId": "0c5e2f48-1d9a-4ad1-9f0d-32a0d11a8a20",
"unitId": null,
"tenantId": "02ea27af-05c0-4af6-be75-8cd17bdf6340",
"resourceType": "tenant",
"resourceId": "02ea27af-05c0-4af6-be75-8cd17bdf6340",
"ip": "203.0.113.42",
"clientId": "mm-portal-web",
"metadata": { "plan": "enterprise" },
"before": null,
"after": { "name": "Clínica Exemplo" },
"occurredAt": "2026-04-09T00:00:00.000Z",
"createdAt": "2026-04-09T00:00:00.000Z"
}
],
"page": 1,
"limit": 20,
"total": 1,
"totalPages": 1
}

Campos internos da cadeia de hash (hash, previousHash, chainScope, seq, hashVersion, idempotencyKey) não são expostos por esta rota — confirmado pelos testes de contrato (query-list.e2e.spec.ts, "never exposes the chain internals").

metadata, before e after passam pelo mascaramento antes de sair; podem conter identificadores não cobertos por nenhuma regra configurada — não trate a ausência de marcador como garantia de que o campo não tem dado sensível.

Erros​

Classe de erroerrorCodeStatusQuando ocorre
UnauthenticatedErrorUNAUTHENTICATED401Sem token ou token inválido
ForbiddenActionAUTHORIZATION_CLAIM_UNAVAILABLE403Ator sem claim de autorização (ex.: sem vínculo ativo)
ForbiddenActionFORBIDDEN_ACTION403Sem audit:read no escopo pedido, ou tenantId/unitId do path fora do que foi concedido ao ator
ForbiddenActionAUTHORIZATION_SCOPE_UNAVAILABLE403tenantId/unitId do path não corresponde a um tenant/unidade ativo
(validação de payload)—400tenantId/unitId não é UUID; to anterior a from; resourceType sem resourceId (ou vice-versa); paginação fora dos limites

Regras de negócio​

IDRegraComportamento esperado
RN-01Sem tenant acessível → página vaziaNão é erro; a rota geral devolve data: [], total: 0
RN-02Rotas de tenant/unidade nunca mostram ações de usuárioresourceType: user é sempre excluído do filtro base, e um filtro explícito por user devolve página vazia
RN-03Ordenação é por ocorrência, desempatada pela cadeiaoccurredAt DESC, createdAt DESC, seq DESC
RN-04Mascaramento é por nome de campo, case-insensitive, com regra coringaUma regra na entidade * mascara aquele nome de campo em qualquer evento; uma regra em diagnosis só se aplica a eventos com prefixo diagnosis.
RN-05Mascaramento cobre subárvores inteirasSe a chave mascarada aponta para um objeto/array, todo o conteúdo abaixo dela é mascarado, não só o valor de topo
RN-06event, resourceId etc. são normalizados (trim) antes do filtroFiltro em branco é tratado como ausente

Compliance​

Órgão / normaExigênciaComo a rota atende
LGPD (Art. 5/6)minimização de dados pessoais expostosmascaramento por regra configurável (CPF, e-mail, telefone, senha) aplicado em toda leitura, independente de quem pergunta
HIPAA (identificador #15 — IP)tratar IP como dado identificávelcampo ip documentado como dado pessoal e sujeito às mesmas regras de mascaramento
LGPD/dado clíniconão expor conteúdo de laudo (PHI) sem necessidaderegra de mascaramento dedicada para a entidade diagnosis (nome/identidade/data de nascimento/e-mail do paciente e o próprio conteúdo do laudo em HTML/OIT/mamografia/eco)

Variáveis de ambiente​

VariávelUsoDefault
AUDIT_MASKING_CACHE_TTL_SECONDStempo de cache do conjunto de regras de mascaramento em memória300s
AUDIT_API_PORTporta HTTP do microsserviço de auditoria3001

Tempo médio de resposta​

A confirmar — responsável: time de Audit; data: 24/09/2026. Não há medição publicada; não foi executado neste levantamento.

Requisitos não funcionais​

RequisitoDefinição
IdempotênciaSim — GET sem efeito colateral
PaginaçãoSim — page/limit, limite máximo de 100 itens por página
Rate limitAplicado no nível do microsserviço (RateLimitModule); limites não documentados nesta página — ver configuração de ambiente do serviço
CacheApenas o conjunto de regras de mascaramento (TTL configurável); os dados de auditoria em si não são cacheados
AuditoriaNão se aplica — esta é a própria rota de consulta de auditoria

Consumo pelo BFF (GraphQL)​

O frontend nunca chama esta API REST diretamente. O BFF expõe uma única query GraphQL, auditLogs, que decide qual das três rotas REST chamar (unitId informado → rota de unidade; senão tenantId → rota de tenant; senão → rota geral) e adiciona duas coisas que a API REST não tem:

  • Autenticação por sessão de navegador (BffAuthGuard): em vez de um Bearer token do cliente, o BFF lê o cookie de sessão do navegador, valida a sessão, obtém um access token de serviço e só então chama a API de auditoria com esse token — o frontend não manipula token de acesso.
  • Hidratação de nomes e diff de campos: a resposta GraphQL adiciona user, unit, tenant, resource e application (nomes legíveis resolvidos por chamadas aos diretórios de usuário/organização/papéis, com fallback para valores presentes em metadata/before/after) e um campo changes — a lista de campos de before/after que realmente mudaram, já formatada como texto para exibição, calculada comparando as duas snapshots campo a campo.

Filtros aceitos pela query auditLogs (AuditLogListRequest): tenantId, unitId, event, user (busca por nome/e-mail, resolvida para um userId antes de consultar a API), userId, from, to, resourceType, resourceId, page, limit — o mesmo contrato de filtros da API REST, com limit limitado a 100 também no BFF.

O tipo AuditLogPageType expõe items (nome atual) e mantém data como alias descontinuado (@deprecated, "renomeado para items") apenas por compatibilidade.

Relacionado​