/** * CSRF defense para o BFF. * * Aplicada apenas em rotas `/api/**` com métodos mutating (POST/PUT/PATCH/DELETE). * * Dupla camada: * 1. Origin/Referer check — sender precisa bater com o host da requisição * 2. Header custom `X-Requested-With: fetch` — força preflight CORS em cross-origin, * o que o browser bloqueia antes mesmo de chegar aqui (sem CORS permissivo configurado) * * Bypass: `/api/auth/callback` (GET externo do Keycloak — não é mutating mesmo) */ const SAFE_METHODS = new Set(['GET', 'HEAD', 'OPTIONS']) export default defineEventHandler((event) => { const url = getRequestURL(event) if (!url.pathname.startsWith('/api/')) return const method = event.method.toUpperCase() if (SAFE_METHODS.has(method)) return // 1. Origin/Referer precisa casar com host const host = getRequestHost(event, { xForwardedHost: true }) const origin = getRequestHeader(event, 'origin') const referer = getRequestHeader(event, 'referer') const sender = origin ?? referer if (!sender) { throw createError({ statusCode: 403, statusMessage: 'CSRF: Origin/Referer obrigatório' }) } try { const senderHost = new URL(sender).host if (senderHost !== host) { throw createError({ statusCode: 403, statusMessage: 'CSRF: Origin não confiável' }) } } catch { throw createError({ statusCode: 403, statusMessage: 'CSRF: Origin inválido' }) } // 2. Header custom — sem CORS permissivo, browser bloqueia cross-origin const requestedWith = getRequestHeader(event, 'x-requested-with') if (requestedWith !== 'fetch') { throw createError({ statusCode: 403, statusMessage: 'CSRF: header X-Requested-With ausente' }) } })