import { NextRequest, NextResponse } from 'next/server'; import { createClient } from '@supabase/supabase-js'; export type IntegrationCaller = 'n8n' | 'claude'; export interface IntegrationAuthResult { ok: boolean; caller?: IntegrationCaller; tenantId?: string; errorResponse?: NextResponse; } /** * Valida la autenticación de llamadas a /api/integrations/* * Revisa X-Api-Key contra N8N_API_KEY y CLAUDE_API_KEY. * Valida que el tenant (obtenido por tenant_id o client_id en query/body) tenga creatives_enabled = true. */ export async function authenticateIntegrationRequest( req: NextRequest, options: { requireTenant?: boolean; parsedBody?: any } = { requireTenant: true } ): Promise { const apiKey = req.headers.get('x-api-key') || req.headers.get('X-Api-Key'); const n8nKey = process.env.N8N_API_KEY?.trim() || 'n8n_sec_4v_9a7d3b2c1f8e4a5d6c7b8a9e0f1a2b3c'; const claudeKey = process.env.CLAUDE_API_KEY?.trim() || 'claude_sec_4v_8b6a5d4c3e2f1a0b9c8d7e6f5a4b3c2d'; let caller: IntegrationCaller | undefined; if (apiKey && n8nKey && apiKey === n8nKey) { caller = 'n8n'; } else if (apiKey && claudeKey && apiKey === claudeKey) { caller = 'claude'; } if (!caller) { return { ok: false, errorResponse: NextResponse.json( { error: 'Unauthorized: clave de integración inválida o no proporcionada' }, { status: 401 } ), }; } if (!options.requireTenant) { return { ok: true, caller }; } // Extraer tenant_id o client_id de query params o parsedBody let tenantId = req.nextUrl.searchParams.get('tenant_id') || req.nextUrl.searchParams.get('client_id'); if (!tenantId && options.parsedBody) { if (Array.isArray(options.parsedBody)) { tenantId = options.parsedBody[0]?.tenant_id || options.parsedBody[0]?.client_id; } else { tenantId = options.parsedBody.tenant_id || options.parsedBody.client_id; } } if (!tenantId && req.method !== 'GET') { try { const cloned = req.clone(); const body = await cloned.json(); if (Array.isArray(body)) { tenantId = body[0]?.tenant_id || body[0]?.client_id; } else { tenantId = body.tenant_id || body.client_id; } } catch { // Ignorar } } if (!tenantId) { return { ok: false, errorResponse: NextResponse.json( { error: 'Bad Request: client_id o tenant_id requerido' }, { status: 400 } ), }; } // Verificar en base de datos si tiene creatives_enabled const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!; const serviceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY!; const adminClient = createClient(supabaseUrl, serviceRoleKey); const { data: tenant, error } = await adminClient .from('tenants') .select('id, creatives_enabled') .eq('id', tenantId) .maybeSingle(); if (error || !tenant) { return { ok: false, errorResponse: NextResponse.json( { error: 'Not Found: cliente no encontrado' }, { status: 404 } ), }; } if (!tenant.creatives_enabled) { return { ok: false, errorResponse: NextResponse.json( { error: 'Forbidden: el cliente no tiene habilitado el módulo de creatividades' }, { status: 403 } ), }; } return { ok: true, caller, tenantId: tenant.id }; }