Skip to main content

Módulo Audit — visão geral

O módulo de auditoria (@mobilemed/audit) grava e consulta a trilha de eventos do Portal 2.0: quem fez o quê, quando, sobre qual recurso e a partir de qual IP/aplicação. Diferente de um log de depuração, ele existe para responder, meses depois, "o que aconteceu com este registro" — para suporte, investigação de incidente e conformidade (LGPD, HIPAA, ANVISA).

O módulo não tem rota própria de gravação. Qualquer domínio do sistema (exames, usuários, tenants/unidades, papéis, integrações, etc.) publica um evento assíncrono descrevendo a mudança; o Audit consome esse evento, encadeia a entrada por hash em uma cadeia apensável (append-only) e persiste em um banco Postgres próprio. A consulta é feita por uma API REST dedicada, com mascaramento de campos sensíveis aplicado na leitura, e o frontend acessa essa API somente através de um resolver GraphQL no BFF — nunca diretamente.

Nota sobre o sistema legado. Os cards de negócio que descrevem "auditoria" no legado (mm-pacs-portal-api / mm-pacs-public-api) tratam de coleções MongoDB separadas por domínio (exameHistorico, EmpresaHistorico, UsuarioLog, GrupoUsuarioLog), sem cadeia de hash, com PHI/PII gravado e devolvido em claro, e com pelo menos um endpoint de consulta sem autenticação. Este módulo substitui essa arquitetura por completo por uma trilha única e genérica, independente de domínio. Não presuma que qualquer comportamento específico do legado (dicionário fixo de eventos, contagem de chamadas, etc.) ainda se aplica — o que este módulo garante hoje é descrito abaixo e nas páginas de cada capacidade, a partir do código atual.

Onde o serviço roda​

O Audit é um microsserviço próprio (app/audit), separado da API principal do Portal 2.0 (app/main, porta 3000). Em desenvolvimento local ele sobe na porta 3001 (AUDIT_API_PORT, default 3001) e expõe:

  • API REST de consulta em http://localhost:3001/v1/... (versionamento por URI, VersioningType.URI);
  • Swagger em http://localhost:3001/docs;
  • um consumidor RabbitMQ (mesmo processo, via app.connectMicroservice) que ouve a fila de eventos de auditoria — não é uma rota HTTP.

O BFF GraphQL (consumido pelo frontend) roda dentro da API principal e chama esta API REST internamente através de AuditApiClient, usando a URL configurada em bff.auditApiUrl.

Arquitetura​

As três camadas do pacote​

PastaResponsabilidade
record/Lado de gravação: consumidor de fila, mapeamento da mensagem, apensação na cadeia de hash. Não expõe HTTP.
query/Lado de leitura: AuditLogController (REST), serviços de listagem e verificação de integridade, mascaramento de campos na leitura.
shared/Domínio compartilhado pelos dois lados: AuditEntry/AuditMessage, ChainLink/ChainVerification, hash SHA-256, assinatura HMAC dos checkpoints, entidades TypeORM e enums (AuditResourceType, ChainBreach, MaskableField).

Cadeia de hash (o que resolve uma lacuna real do legado)​

Cada entrada persistida carrega um hash calculado sobre seu próprio conteúdo e o previousHash da entrada anterior na mesma cadeia — uma cadeia por escopo (chainScope): o tenantId do evento, ou __system__ quando não há tenant. Além disso, cada cadeia tem um checkpoint (audit_chain_checkpoints): o hash da última entrada, a contagem de entradas e uma assinatura HMAC sobre esses dois valores, para detectar até a substituição do próprio checkpoint. Ver Verificar integridade para o que exatamente é detectado (elo quebrado = possível fork/exclusão; hash que não bate, checkpoint ausente/forjado/divergente = adulteração).

Isso não existia no legado descrito nos cards (trilha em Mongo, sem hash-chain, com possibilidade de apagar uma entrada sem deixar rastro) — é uma melhoria confirmada no código atual, não uma suposição.

Mascaramento na leitura (o que resolve outra lacuna real do legado)​

O conteúdo é gravado sem mascaramento (a cadeia de hash precisa do conteúdo original para se verificar). O mascaramento acontece na leitura, aplicado pelo AuditLogMaskingService sobre uma tabela de regras (audit_masking_rules, entidade × campo, cacheada em memória por AUDIT_MASKING_CACHE_TTL_SECONDS, default 300s). Isso troca o dado real por um marcador (***@***.***, ***.***.***-**, ***-***-**** ou ***, conforme o formato do valor) sem alterar o que está persistido. Duas cargas de regra já vêm por migração:

  • regra coringa (entidade *): cpf, cns, rg, email, phone, telefone, celular, password, senha — aplicada a qualquer evento, em metadata, before, after, descrição, IP do ator, userId, targetUserId, clientId e resourceId quando o nome do campo bate;
  • regra da entidade diagnosis (eventos com prefixo diagnosis.*): patientName, patientIdentity, patientBirthDate, patientEmail, accessionNumber, studyInstanceUid, patientCode, html, oit, mammography, echocardiogram — cobre justamente o conteúdo clínico do laudo (o "PHI em claro" apontado no legado).

O conjunto de regras é dado, não código: pode crescer sem alterar o serviço. Ver Consultar logs para como o mascaramento se aplica por rota.

Um evento genérico para qualquer domínio​

Ao contrário do legado (uma coleção por domínio — exame, empresa, usuário, grupo), a gravação é cross-domain: qualquer publicador envia a mesma forma de mensagem (AuditCreatedEvent, com uma ou mais entries), e cada entrada carrega seu próprio resourceType (enum AuditResourceType: exam, user, tenant, unit, role, policy, oauth_client, integration_instance, etc.) e resourceId. A consulta filtra por esse par (resourceType + resourceId) para reconstruir o histórico de um recurso específico — é o equivalente funcional a "trilha por exame" ou "trilha por empresa" do legado, só que sobre uma única tabela. Ver Registrar evento (fluxo assíncrono).

Regra de visibilidade entre rotas​

As rotas tenants/:tenantId/audit-logs e units/:unitId/audit-logs nunca retornam entradas com resourceType: user — mesmo que o chamador peça esse filtro explicitamente, a resposta vem vazia. Ações sobre usuário só aparecem na rota geral (GET /audit-logs), dentro dos tenants a que o chamador tem acesso. Isso é uma regra confirmada no código (AuditLogVisibilityScope.excludesUserResources), não uma suposição de produto.

Páginas deste módulo​

PáginaCobre
Consultar logsGET audit-logs, GET tenants/:tenantId/audit-logs, GET units/:unitId/audit-logs e o resolver GraphQL auditLogs do BFF
Verificar integridadeGET audit-logs/integrity — verificação da cadeia de hash e dos checkpoints assinados
Registrar eventoFluxo assíncrono de gravação: fila → verificação de assinatura → apensação na cadeia → Postgres

:::tip OpenAPI A documentação interativa (schemas + "Try it out") está disponível em /docs no serviço de auditoria (local: http://localhost:3001/docs). :::