118 lines
3.3 KiB
TypeScript
118 lines
3.3 KiB
TypeScript
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<IntegrationAuthResult> {
|
|
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 };
|
|
}
|