/** * Input Validation Middleware with Zod * Vulnerabilidade 2.4 - Adicionar validação de input * @author Descomplicar® | @link descomplicar.pt | @copyright 2026 */ import { z } from 'zod' import type { Request, Response, NextFunction } from 'express' /** * Middleware genérico de validação Zod */ export function validateRequest(schema: { body?: z.ZodSchema params?: z.ZodSchema query?: z.ZodSchema }) { return async (req: Request, res: Response, next: NextFunction) => { try { // Validar body if (schema.body) { req.body = await schema.body.parseAsync(req.body) as any } // Validar params if (schema.params) { req.params = await schema.params.parseAsync(req.params) as any } // Validar query if (schema.query) { req.query = await schema.query.parseAsync(req.query) as any } next() } catch (error: unknown) { if (error instanceof z.ZodError) { return res.status(400).json({ error: 'Validation error', details: error.issues.map((e: z.ZodIssue) => ({ field: e.path.join('.'), message: e.message })) }) } // Outros erros return res.status(500).json({ error: 'Internal validation error' }) } } } // ============================================================================ // Schemas de validação para rotas comuns // ============================================================================ /** * Schema para WordPress Monitor POST */ export const wpMonitorSchema = { body: z.object({ site_url: z.string().url('Invalid site_url format'), site_name: z.string().optional(), health: z.object({ status: z.string().optional() }).passthrough().optional(), updates: z.object({ counts: z.object({ total: z.number().int().nonnegative() }).passthrough().optional(), core: z.any().optional(), plugins: z.any().optional(), themes: z.any().optional() }).passthrough().optional(), system: z.object({ debug_mode: z.boolean().optional() }).passthrough().optional(), database: z.object({ size_mb: z.number().nonnegative().optional() }).passthrough().optional() }).passthrough() // Permite campos adicionais } /** * Schema para server metrics params */ export const serverMetricsParamsSchema = { params: z.object({ server_id: z.string().regex(/^\d+$/, 'server_id must be numeric') }) } /** * Schema para server metrics query */ export const serverMetricsQuerySchema = { query: z.object({ hours: z.string().regex(/^\d+$/, 'hours must be numeric').optional().default('24') }) } /** * Schema para dashboard query (semana) */ export const dashboardWeekSchema = { query: z.object({ week: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'week must be YYYY-MM-DD format').optional() }) } /** * Schema para Hetzner server ID */ export const hetznerServerIdSchema = { params: z.object({ server_id: z.string().regex(/^\d+$/, 'server_id must be numeric') }) } /** * Schema para financial query (intervalo de datas) */ export const financialDateRangeSchema = { query: z.object({ start: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'start must be YYYY-MM-DD').optional(), end: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'end must be YYYY-MM-DD').optional(), month: z.string().regex(/^\d{4}-\d{2}$/, 'month must be YYYY-MM').optional() }) }