feat: rewrite tenant middleware for pool-based tenant resolution
- Resolve tenant DB via TenantConnectionManager instead of SET search_path - Add tenantPool to Express Request for direct pool queries - Keep tenantSchema as backward compat until all services are migrated - Support admin impersonation via X-View-Tenant header Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,48 +1,61 @@
|
||||
import type { Request, Response, NextFunction } from 'express';
|
||||
import { prisma } from '../config/database.js';
|
||||
import { AppError } from './error.middleware.js';
|
||||
import type { Pool } from 'pg';
|
||||
import { prisma, tenantDb } from '../config/database.js';
|
||||
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface Request {
|
||||
tenantSchema?: string;
|
||||
tenantPool?: Pool;
|
||||
tenantSchema?: string; // @deprecated - use tenantPool instead
|
||||
viewingTenantId?: string;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function tenantMiddleware(req: Request, res: Response, next: NextFunction) {
|
||||
if (!req.user) {
|
||||
return next(new AppError(401, 'No autenticado'));
|
||||
}
|
||||
|
||||
try {
|
||||
// Check if admin is viewing a different tenant
|
||||
const viewTenantId = req.headers['x-view-tenant'] as string | undefined;
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ message: 'No autenticado' });
|
||||
}
|
||||
|
||||
let tenantId = req.user.tenantId;
|
||||
let databaseName = req.user.databaseName;
|
||||
|
||||
// Only admins can view other tenants
|
||||
if (viewTenantId && req.user.role === 'admin') {
|
||||
tenantId = viewTenantId;
|
||||
req.viewingTenantId = viewTenantId;
|
||||
// Admin impersonation via X-View-Tenant header
|
||||
const viewTenantHeader = req.headers['x-view-tenant'] as string;
|
||||
if (viewTenantHeader && req.user.role === 'admin') {
|
||||
const viewedTenant = await prisma.tenant.findFirst({
|
||||
where: {
|
||||
OR: [
|
||||
{ id: viewTenantHeader },
|
||||
{ rfc: viewTenantHeader },
|
||||
],
|
||||
},
|
||||
select: { id: true, databaseName: true, active: true },
|
||||
});
|
||||
|
||||
if (!viewedTenant) {
|
||||
return res.status(404).json({ message: 'Tenant no encontrado' });
|
||||
}
|
||||
|
||||
if (!viewedTenant.active) {
|
||||
return res.status(403).json({ message: 'Tenant inactivo' });
|
||||
}
|
||||
|
||||
tenantId = viewedTenant.id;
|
||||
databaseName = viewedTenant.databaseName;
|
||||
req.viewingTenantId = viewedTenant.id;
|
||||
}
|
||||
|
||||
const tenant = await prisma.tenant.findUnique({
|
||||
where: { id: tenantId },
|
||||
select: { databaseName: true, active: true },
|
||||
});
|
||||
// New pool-based approach
|
||||
req.tenantPool = tenantDb.getPool(tenantId, databaseName);
|
||||
|
||||
if (!tenant || !tenant.active) {
|
||||
return next(new AppError(403, 'Tenant no encontrado o inactivo'));
|
||||
}
|
||||
|
||||
req.tenantSchema = tenant.databaseName;
|
||||
|
||||
// Set search_path for this request (will be replaced by pool-based approach)
|
||||
await prisma.$executeRawUnsafe(`SET search_path TO "${tenant.databaseName}", public`);
|
||||
// Backward compat: tenantSchema still used by controllers until Task 8 migration
|
||||
req.tenantSchema = databaseName;
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
next(new AppError(500, 'Error al configurar tenant'));
|
||||
console.error('[TenantMiddleware] Error:', error);
|
||||
return res.status(500).json({ message: 'Error al resolver tenant' });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user