Security: Corrigir 3 vulnerabilidades críticas + 1 moderada

[C-001] CRÍTICO - Implementar autenticação API key
- Middleware Next.js protege todas as rotas /api/* (exceto /health)
- Sistema auth com validação de header x-api-key
- Template .env.example com API_SECRET_KEY

[C-002] CRÍTICO - Validação de inputs com Zod
- Schemas para siteId (int positivo) e period (1-365d)
- Previne NaN, SQL injection, inputs maliciosos
- Respostas 400 Bad Request com detalhes de erro

[C-003] CRÍTICO - Corrigir TypeScript any type
- chart-card.tsx: any → string | number | null
- ESLint passa sem warnings

[M-005] MODERADO - Corrigir .gitignore sobre-restritivo
- Exceção !.env.example permite commit do template

Verificações:
 pnpm run lint - 0 erros
 pnpm audit - 0 vulnerabilidades
 CVSS 7.5 → 0.0

Docs: AUDIT-REPORT.md, SECURITY-FIX.md, CHANGELOG.md
Regra: #47 (Security Audit Pre-Commit)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-14 03:24:54 +00:00
parent bc23c4213c
commit 1f7dc5ff2b
10 changed files with 349 additions and 6 deletions

View File

@@ -1,5 +1,7 @@
import { prisma } from '@/lib/prisma'
import { NextResponse } from 'next/server'
import { siteIdSchema, periodSchema } from '@/lib/validations'
import { z } from 'zod'
/**
* GET /api/metrics/[siteId]?period=30d
@@ -10,18 +12,20 @@ export async function GET(
{ params }: { params: Promise<{ siteId: string }> }
) {
try {
const { siteId } = await params
const { siteId: rawSiteId } = await params
const { searchParams } = new URL(request.url)
const period = searchParams.get('period') || '30d'
const rawPeriod = searchParams.get('period') || '30d'
// Validate inputs with Zod
const { siteId } = siteIdSchema.parse({ siteId: rawSiteId })
const { period: days } = periodSchema.parse({ period: rawPeriod })
// Parse period (30d, 90d, 7d)
const days = parseInt(period.replace('d', ''))
const startDate = new Date()
startDate.setDate(startDate.getDate() - days)
// Get site info
const site = await prisma.site.findUnique({
where: { id: parseInt(siteId) }
where: { id: siteId }
})
if (!site || !site.ga4PropertyId) {
@@ -198,6 +202,18 @@ export async function GET(
}
})
} catch (error) {
// Handle Zod validation errors
if (error instanceof z.ZodError) {
return NextResponse.json(
{
success: false,
error: 'Invalid input',
details: error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ')
},
{ status: 400 }
)
}
console.error('Error fetching metrics:', error)
return NextResponse.json(
{