fix(security): harden auth, tenant scoping, and scrub secrets

Require session on APIs, scope KPIs/activos/simulate to the user tenant, strip real JWTs from .env.example, and add health/smoke/docs for 4Ventures ops.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
4Ventures
2026-09-21 10:51:46 +02:00
parent e7b6ab71ca
commit 2df3fe6983
21 changed files with 621 additions and 276 deletions

View File

@@ -3,9 +3,26 @@ import { createClient } from '@/lib/supabase/server';
export async function POST(req: NextRequest) {
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || 'https://supabase.4ventures.es';
const serviceKey = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || '';
// Prefer user JWT for tenant-scoped reads; service role only if explicitly set (server)
const serviceKey = process.env.SUPABASE_SERVICE_ROLE_KEY?.trim() || '';
// 1. Extraer mensaje del usuario con compatibilidad para cualquier estructura de body
const supabase = await createClient();
const { data: { user } } = await supabase.auth.getUser();
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const tenantId =
user.app_metadata?.tenant_id || user.user_metadata?.tenant_id || null;
const companyName = user.user_metadata?.company || 'Cliente';
if (!tenantId) {
return NextResponse.json(
{ error: 'Usuario sin tenant_id en metadata. Re-provisionar cliente.' },
{ status: 403 }
);
}
// 1. Extraer mensaje del usuario
let userMessage = '';
try {
const body = await req.json();
@@ -23,7 +40,6 @@ export async function POST(req: NextRequest) {
console.error('Error parsing chat body:', e);
}
// Normalizar consulta eliminando tildes, signos de interrogación y puntuación
const normQuery = userMessage
.toLowerCase()
.normalize('NFD')
@@ -32,24 +48,11 @@ export async function POST(req: NextRequest) {
.replace(/\s+/g, ' ')
.trim();
// 2. Obtener tenant_id dinámico del usuario autenticado
let tenantId = '0b3c5fc0-6901-4caf-b26a-b6a0201d8541';
let companyName = '4Ventures Central';
try {
const supabase = await createClient();
const { data: { user } } = await supabase.auth.getUser();
if (user) {
tenantId = user.app_metadata?.tenant_id || user.user_metadata?.tenant_id || tenantId;
companyName = user.user_metadata?.company || companyName;
}
} catch (e) {
console.error('Error resolving user tenant in chat:', e);
}
// Use service role for RPC only after authn; still filter by tenant_id in every call
const keyForRpc = serviceKey || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || '';
const headers = {
apikey: serviceKey,
Authorization: `Bearer ${serviceKey}`,
apikey: keyForRpc,
Authorization: `Bearer ${keyForRpc}`,
'Content-Type': 'application/json'
};