From 05a3a966e50c2b6be871cf6f376101c068418ba2 Mon Sep 17 00:00:00 2001 From: "Diego Ferreira L.G.Oliveira" Date: Thu, 4 Jun 2026 16:38:01 -0300 Subject: [PATCH] =?UTF-8?q?feat(seguranca):=20auditoria=20OWASP=20API=20To?= =?UTF-8?q?p=2010=20e=20corre=C3=A7=C3=A3o=20de=20vulnerabilidades?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/compartilhado/utilitarios/criptografia.ts | 6 ++++-- src/infra/http/aplicacao.ts | 16 ++++++++++++---- .../usuarios/apresentacao/rotas/rotas-usuario.ts | 9 ++++++--- .../repositorios/repositorio-visitante-mongo.ts | 8 ++++++-- 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/compartilhado/utilitarios/criptografia.ts b/src/compartilhado/utilitarios/criptografia.ts index 1b4b4e3..948a832 100644 --- a/src/compartilhado/utilitarios/criptografia.ts +++ b/src/compartilhado/utilitarios/criptografia.ts @@ -1,4 +1,4 @@ -import { scrypt, randomBytes } from 'crypto' +import { scrypt, randomBytes, timingSafeEqual } from 'crypto' import { promisify } from 'util' const scryptAsync = promisify(scrypt) @@ -13,5 +13,7 @@ export async function verificarSenha(senha: string, hash: string): Promise { disableRequestLogging: true, }) as unknown as FastifyInstance + function sanitizarUrl(url: string): string { + return url.replace(/([?&]cpf=)[^&]*/gi, '$1[OMITIDO]') + } + app.addHook('onRequest', (request, _reply, done) => { - request.log.info({ req: { method: request.method, url: request.url } }, 'incoming request') + request.log.info({ req: { method: request.method, url: sanitizarUrl(request.url) } }, 'incoming request') done() }) app.addHook('onResponse', (request, reply, done) => { request.log.info( { - req: { method: request.method, url: request.url }, + req: { method: request.method, url: sanitizarUrl(request.url) }, res: { statusCode: reply.statusCode }, responseTime: reply.elapsedTime, }, @@ -70,8 +74,12 @@ export async function criarAplicacao(): Promise { }) await app.register(cookie) + const jwtSecreto = process.env.JWT_SECRETO + if (!jwtSecreto || jwtSecreto.length < 32) { + throw new Error('JWT_SECRETO não definido ou com menos de 32 caracteres.') + } await app.register(jwt, { - secret: process.env.JWT_SECRETO ?? '', + secret: jwtSecreto, sign: { expiresIn: process.env.JWT_EXPIRACAO_ACCESS ?? '1h' }, }) @@ -148,7 +156,7 @@ export async function criarAplicacao(): Promise { app.get('/saude', () => ({ sucesso: true, - dados: { status: 'ok', ambiente: process.env.NODE_ENV }, + dados: { status: 'ok' }, })) return app diff --git a/src/modulos/usuarios/apresentacao/rotas/rotas-usuario.ts b/src/modulos/usuarios/apresentacao/rotas/rotas-usuario.ts index b68a631..d670deb 100644 --- a/src/modulos/usuarios/apresentacao/rotas/rotas-usuario.ts +++ b/src/modulos/usuarios/apresentacao/rotas/rotas-usuario.ts @@ -1,5 +1,6 @@ import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify' import type { ZodTypeProvider } from 'fastify-type-provider-zod' +import { z } from 'zod' import type { ControladorUsuario } from '@/modulos/usuarios/apresentacao/controladores/controlador-usuario' import { schemaCadastrarUsuario, @@ -41,11 +42,13 @@ export function rotasUsuario( }) rotas.patch('/:id', { - schema: { body: schemaAtualizarUsuario }, + schema: { + params: z.object({ id: z.string().length(24, 'ID deve ter 24 caracteres.') }), + body: schemaAtualizarUsuario, + }, preHandler: [autenticar(app), autorizarAdmin(app)], handler: async (req, reply) => { - const { id } = req.params as { id: string } - const dados = await controlador.atualizar(id, req.body, req.user.usuarioId) + const dados = await controlador.atualizar(req.params.id, req.body, req.user.usuarioId) return reply.status(200).send({ sucesso: true, dados }) }, }) diff --git a/src/modulos/visitantes/infra/repositorios/repositorio-visitante-mongo.ts b/src/modulos/visitantes/infra/repositorios/repositorio-visitante-mongo.ts index 5163005..2531851 100644 --- a/src/modulos/visitantes/infra/repositorios/repositorio-visitante-mongo.ts +++ b/src/modulos/visitantes/infra/repositorios/repositorio-visitante-mongo.ts @@ -26,6 +26,10 @@ interface DocumentoVisitante { atualizadoPor: ObjectId } +function escaparRegex(texto: string): string { + return texto.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') +} + export class RepositorioVisitanteMongo implements RepositorioVisitante { private readonly colecao: Collection @@ -97,7 +101,7 @@ export class RepositorioVisitanteMongo implements RepositorioVisitante { } if (filtros.setorDestinoId) query.setorDestinoId = filtros.setorDestinoId if (filtros.nomeCompleto) { - query.nomeCompleto = { $regex: filtros.nomeCompleto, $options: 'i' } + query.nomeCompleto = { $regex: escaparRegex(filtros.nomeCompleto), $options: 'i' } } const skip = (filtros.pagina - 1) * filtros.itensPorPagina @@ -113,7 +117,7 @@ export class RepositorioVisitanteMongo implements RepositorioVisitante { const query: Filter = {} if (filtros.cpf) query.cpf = filtros.cpf if (filtros.nomeCompleto) { - query.nomeCompleto = { $regex: filtros.nomeCompleto, $options: 'i' } + query.nomeCompleto = { $regex: escaparRegex(filtros.nomeCompleto), $options: 'i' } } const skip = (filtros.pagina - 1) * filtros.itensPorPagina