Multi-tenant

Toda query no banco e tenant-scoped por municipioId. Violar isso e considerado bug critico de seguranca (LGPD + responsabilidade contratual).

Como funciona

  1. TenantMiddleware valida JWT, extrai municipioId e abre TenantContext.run(...).
  2. AsyncLocalStorage propaga o contexto em toda chain async.
  3. GovonPrismaService.db lanca ForbiddenError se nao houver tenant ativo.
  4. scopedWhere/scopedData injetam municipioId e rejeitam divergencia entre header/body e contexto.

Uso em Service

import { scopedWhere } from '@govon/data';

async listar() {
  return this.prisma.db.usuario.findMany({
    where: scopedWhere({ ativo: true }),
  });
}

// Tentativa de cross-tenant joga:
this.prisma.db.usuario.findMany({
  where: scopedWhere({ municipioId: 'outro-municipio' })
});
// Error: cross-tenant

Vetores de ataque cobertos

  • Header ?municipioId=X ignorado em prod (so JWT vale)
  • Body com municipioId divergente: scopedData rejeita
  • switch-tenant para municipio sem vinculo: 403 AUTH_NO_TENANT_ACCESS
  • AuditLog le municipioId do TenantContext, nunca do header

Ver politica completa em docs/MULTI_TENANT_POLICY.md.