Bloqueio de IP e Security
Catch-all para rotas inválidas, repositories CQRS-ready (Postgres ativo) e API admin em /api/v1/security.
Visão geral
O módulo em src/common/security registra acessos a rotas de API desconhecidas e pode bloquear IPs. Persistência CQRS-ready: Postgres é o store ativo via BlockedIpPostgresRepository; o repository Mongo fica registrado mas não é usado (mesmo padrão do users). A gestão é feita por endpoints REST que exigem JWT e a role super.
Ideias-chave
- • CatchAllController registra tentativas em rotas inválidas (só production/staging)
- • BlockedIpPostgresRepository é o store ativo; repo Mongo é CQRS-ready / não usado
- • Após MAX_ATTEMPTS, o IP é bloqueado (403 nas próximas)
- • API admin: listar, desbloquear, resetar, remover — JWT + Role.SUPER
Estrutura
src/common/security/ ├── security.module.ts ├── security.controller.ts ├── security.service.ts ├── catchall.controller.ts ├── repositories/ │ ├── postgres.repository.ts │ └── mongo.repository.ts ├── entities/blocked-ip.entity.ts ├── schemas/blocked-ip.schema.ts └── interfaces/blocked-ip.interface.ts
Catch-all (rotas inválidas)
O CatchAllController só é registrado quando NODE_ENV é production ou staging. Trata rotas não mapeadas com @All('*') e é @Public().
Fluxo
- • Path de API desconhecido chega ao CatchAllController
- • registerInvalidRouteAttempt(ip, path, userAgent)
- • Se attempts ≥ MAX_ATTEMPTS → blocked = true → 403
- • Caso contrário → 404 JSON { message: "Rota não encontrada" }
Não conta como suspeito
- • 404 HTML da wiki (Accept: text/html no browser)
- • Path GraphQL (GRAPHQL_PATH ou /graphql) — rota Apollo Fastify
- • /favicon.ico (e demais assets de favicon), /robots.txt e /health
SecurityService
As regras de domínio ficam no SecurityService; a persistência passa por BlockedIpPostgresRepository. O repository Mongo existe para CQRS futuro, mas não é chamado.
Constantes
- • MAX_ATTEMPTS = 1 (bloqueia após este número de hits inválidos)
- • MAX_PATHS_STORED = 20 (últimos paths guardados no registro)
Métodos principais
- • registerInvalidRouteAttempt — upsert do IP, incrementa attempts, pode bloquear
- • getBlockedIps / getSuspiciousIps — listas admin
- • unblockIp — limpa bloqueio e tentativas
- • resetAttempts — limpa attempts/block/paths
- • removeIp — apaga via repository Postgres
API admin
Todas as rotas em /api/v1/security exigem Authorization: Bearer e Role.SUPER.
Endpoints
- • GET /api/v1/security/blocked-ips — listar IPs bloqueados
- • GET /api/v1/security/suspicious-ips — IPs com tentativas, ainda não bloqueados
- • POST /api/v1/security/unblock/:ip — desbloquear (200 / 404)
- • POST /api/v1/security/reset/:ip — resetar tentativas (200 / 404)
- • DELETE /api/v1/security/:ip — remover registro (200 / 404)
GET /api/v1/security/blocked-ips Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
[
{
"id": "uuid-...",
"ip": "203.0.113.10",
"attempts": 1,
"blocked": true,
"paths": ["/api/v1/unknown"],
"blockedAt": "2026-07-23T12:00:00.000Z"
}
]
POST /api/v1/security/unblock/203.0.113.10 Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
{
"message": "IP 203.0.113.10 desbloqueado com sucesso",
"data": { "id": "uuid-...", "ip": "203.0.113.10", "blocked": false, "attempts": 0 }
}
Nota Docker / production
Com NODE_ENV=production (como no .env.docker), o catch-all está ativo. Hits em paths de API inexistentes podem bloquear seu IP após MAX_ATTEMPTS. Use a API admin (role super) para desbloquear, ou limpe a tabela blocked_ips.