Pre-reordenación

This commit is contained in:
2025-09-05 04:02:39 +00:00
parent 8522d02170
commit 80778c0ed9
10 changed files with 1115 additions and 120 deletions
+250
View File
@@ -10,13 +10,17 @@
"license": "ISC",
"dependencies": {
"chalk": "^5.6.0",
"connect-redis": "^9.0.0",
"cors": "^2.8.5",
"dotenv": "^17.2.1",
"ejs": "^3.1.10",
"express": "^5.1.0",
"express-ejs-layouts": "^2.5.1",
"express-session": "^1.18.2",
"ioredis": "^5.7.0",
"pg": "^8.16.3",
"pg-format": "^1.0.4",
"redis": "^5.8.2",
"serve-favicon": "^2.5.1"
},
"devDependencies": {
@@ -29,6 +33,72 @@
"dev": true,
"license": "MIT"
},
"node_modules/@ioredis/commands": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.3.1.tgz",
"integrity": "sha512-bYtU8avhGIcje3IhvF9aSjsa5URMZBHnwKtOvXsT4sfYy9gppW11gLPT/9oNqlJZD47yPKveQFTAFWpHjKvUoQ==",
"license": "MIT"
},
"node_modules/@redis/bloom": {
"version": "5.8.2",
"resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-5.8.2.tgz",
"integrity": "sha512-855DR0ChetZLarblio5eM0yLwxA9Dqq50t8StXKp5bAtLT0G+rZ+eRzzqxl37sPqQKjUudSYypz55o6nNhbz0A==",
"license": "MIT",
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@redis/client": "^5.8.2"
}
},
"node_modules/@redis/client": {
"version": "5.8.2",
"resolved": "https://registry.npmjs.org/@redis/client/-/client-5.8.2.tgz",
"integrity": "sha512-WtMScno3+eBpTac1Uav2zugXEoXqaU23YznwvFgkPwBQVwEHTDgOG7uEAObtZ/Nyn8SmAMbqkEubJaMOvnqdsQ==",
"license": "MIT",
"dependencies": {
"cluster-key-slot": "1.1.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@redis/json": {
"version": "5.8.2",
"resolved": "https://registry.npmjs.org/@redis/json/-/json-5.8.2.tgz",
"integrity": "sha512-uxpVfas3I0LccBX9rIfDgJ0dBrUa3+0Gc8sEwmQQH0vHi7C1Rx1Qn8Nv1QWz5bohoeIXMICFZRcyDONvum2l/w==",
"license": "MIT",
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@redis/client": "^5.8.2"
}
},
"node_modules/@redis/search": {
"version": "5.8.2",
"resolved": "https://registry.npmjs.org/@redis/search/-/search-5.8.2.tgz",
"integrity": "sha512-cNv7HlgayavCBXqPXgaS97DRPVWFznuzsAmmuemi2TMCx5scwLiP50TeZvUS06h/MG96YNPe6A0Zt57yayfxwA==",
"license": "MIT",
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@redis/client": "^5.8.2"
}
},
"node_modules/@redis/time-series": {
"version": "5.8.2",
"resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-5.8.2.tgz",
"integrity": "sha512-g2NlHM07fK8H4k+613NBsk3y70R2JIM2dPMSkhIjl2Z17SYvaYKdusz85d7VYOrZBWtDrHV/WD2E3vGu+zni8A==",
"license": "MIT",
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@redis/client": "^5.8.2"
}
},
"node_modules/accepts": {
"version": "2.0.0",
"license": "MIT",
@@ -174,11 +244,33 @@
"fsevents": "~2.3.2"
}
},
"node_modules/cluster-key-slot": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
"integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"dev": true,
"license": "MIT"
},
"node_modules/connect-redis": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/connect-redis/-/connect-redis-9.0.0.tgz",
"integrity": "sha512-QwzyvUePTMvEzG1hy45gZYw3X3YHrjmEdSkayURlcZft7hqadQ3X39wYkmCqblK2rGlw+XItELYt6GnyG6DEIQ==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"peerDependencies": {
"express-session": ">=1",
"redis": ">=5"
}
},
"node_modules/content-disposition": {
"version": "1.0.0",
"license": "MIT",
@@ -265,6 +357,15 @@
}
}
},
"node_modules/denque": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.10"
}
},
"node_modules/depd": {
"version": "2.0.0",
"license": "MIT",
@@ -396,6 +497,46 @@
"node_modules/express-ejs-layouts": {
"version": "2.5.1"
},
"node_modules/express-session": {
"version": "1.18.2",
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.2.tgz",
"integrity": "sha512-SZjssGQC7TzTs9rpPDuUrR23GNZ9+2+IkA/+IJWmvQilTr5OSliEHGF+D9scbIpdC6yGtTI0/VhaHoVes2AN/A==",
"license": "MIT",
"dependencies": {
"cookie": "0.7.2",
"cookie-signature": "1.0.7",
"debug": "2.6.9",
"depd": "~2.0.0",
"on-headers": "~1.1.0",
"parseurl": "~1.3.3",
"safe-buffer": "5.2.1",
"uid-safe": "~2.1.5"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/express-session/node_modules/cookie-signature": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
"integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
"license": "MIT"
},
"node_modules/express-session/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/express-session/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/filelist": {
"version": "1.0.4",
"license": "Apache-2.0",
@@ -589,6 +730,30 @@
"version": "2.0.4",
"license": "ISC"
},
"node_modules/ioredis": {
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.7.0.tgz",
"integrity": "sha512-NUcA93i1lukyXU+riqEyPtSEkyFq8tX90uL659J+qpCZ3rEdViB/APC58oAhIh3+bJln2hzdlZbBZsGNrlsR8g==",
"license": "MIT",
"dependencies": {
"@ioredis/commands": "^1.3.0",
"cluster-key-slot": "^1.1.0",
"debug": "^4.3.4",
"denque": "^2.1.0",
"lodash.defaults": "^4.2.0",
"lodash.isarguments": "^3.1.0",
"redis-errors": "^1.2.0",
"redis-parser": "^3.0.0",
"standard-as-callback": "^2.1.0"
},
"engines": {
"node": ">=12.22.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/ioredis"
}
},
"node_modules/ipaddr.js": {
"version": "1.9.1",
"license": "MIT",
@@ -658,6 +823,18 @@
"node": ">=10"
}
},
"node_modules/lodash.defaults": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
"license": "MIT"
},
"node_modules/lodash.isarguments": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
"integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==",
"license": "MIT"
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"license": "MIT",
@@ -783,6 +960,15 @@
"node": ">= 0.8"
}
},
"node_modules/on-headers": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
"integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/once": {
"version": "1.4.0",
"license": "ISC",
@@ -967,6 +1153,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/random-bytes": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
"integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/range-parser": {
"version": "1.2.1",
"license": "MIT",
@@ -998,6 +1193,43 @@
"node": ">=8.10.0"
}
},
"node_modules/redis": {
"version": "5.8.2",
"resolved": "https://registry.npmjs.org/redis/-/redis-5.8.2.tgz",
"integrity": "sha512-31vunZj07++Y1vcFGcnNWEf5jPoTkGARgfWI4+Tk55vdwHxhAvug8VEtW7Cx+/h47NuJTEg/JL77zAwC6E0OeA==",
"license": "MIT",
"dependencies": {
"@redis/bloom": "5.8.2",
"@redis/client": "5.8.2",
"@redis/json": "5.8.2",
"@redis/search": "5.8.2",
"@redis/time-series": "5.8.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/redis-errors": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
"integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/redis-parser": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
"integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==",
"license": "MIT",
"dependencies": {
"redis-errors": "^1.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/router": {
"version": "2.2.0",
"license": "MIT",
@@ -1204,6 +1436,12 @@
"node": ">= 10.x"
}
},
"node_modules/standard-as-callback": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
"integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==",
"license": "MIT"
},
"node_modules/statuses": {
"version": "2.0.2",
"license": "MIT",
@@ -1260,6 +1498,18 @@
"node": ">= 0.6"
}
},
"node_modules/uid-safe": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
"integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
"license": "MIT",
"dependencies": {
"random-bytes": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/undefsafe": {
"version": "2.0.5",
"dev": true,
+4
View File
@@ -16,13 +16,17 @@
},
"dependencies": {
"chalk": "^5.6.0",
"connect-redis": "^9.0.0",
"cors": "^2.8.5",
"dotenv": "^17.2.1",
"ejs": "^3.1.10",
"express": "^5.1.0",
"express-ejs-layouts": "^2.5.1",
"express-session": "^1.18.2",
"ioredis": "^5.7.0",
"pg": "^8.16.3",
"pg-format": "^1.0.4",
"redis": "^5.8.2",
"serve-favicon": "^2.5.1"
},
"keywords": [],
+85
View File
@@ -12,6 +12,14 @@ import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
//Redis
import session from 'express-session';
import { createClient } from 'redis';
import * as connectRedis from 'connect-redis';
const RedisStore = connectRedis.default || connectRedis.RedisStore;
const redis = createClient({ url: process.env.REDIS_URL || 'redis://authentik-redis:6379' });
await redis.connect();
// Variables de Entorno
import dotenv from 'dotenv';
@@ -36,6 +44,18 @@ app.use(express.json());
app.use(express.json({ limit: '1mb' }));
app.use(express.static(path.join(__dirname, 'pages')));
app.use(session({
name: 'sc.sid',
store: new RedisStore({ client: redis, prefix: 'sess:' }),
secret: process.env.SESSION_SECRET || 'change-me',
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
sameSite: 'lax',
secure: process.env.NODE_ENV === 'production',
},
}));
// ----------------------------------------------------------
// Motor de vistas EJS
@@ -72,6 +92,14 @@ const dbConfig = {
const pool = new Pool(dbConfig);
const tenantsPool = new Pool({
host: process.env.TENANTS_HOST || 'dev-tenants',
port: Number(process.env.TENANTS_PORT || 5432),
user: process.env.TENANTS_USER || 'postgres',
password: process.env.TENANTS_PASS || 'postgres',
database: process.env.TENANTS_DB || 'dev-postgres',
});
// ----------------------------------------------------------
// Seguridad: Tablas permitidas
// ----------------------------------------------------------
@@ -103,6 +131,63 @@ async function getClient() {
return client;
}
export async function withTenant(req, res, next) {
const client = await tenantsPool.connect();
try {
await client.query('BEGIN');
const uuid = getTenantUuid(req);
const schema = `schema_tenant_${uuid}`;
// Usa la función helper si la creaste en la DB (recomendado)
// await client.query('SELECT public.f_set_search_path($1)', [schema]);
// Alternativa directa si aún no tienes la función:
await client.query(`SET LOCAL search_path TO ${schema.replace(/"/g, '')}`);
req.pg = client;
req.pgSchema = schema;
next();
} catch (e) {
try { if (client) await client.query('ROLLBACK'); } catch {}
if (client) client.release();
return res.status(400).json({ error: e.message });
}
}
// Cierra la transacción y libera la conexión
export async function done(req, res, next) {
try {
if (req.pg) await req.pg.query('COMMIT');
} catch (e) {
try { if (req.pg) await req.pg.query('ROLLBACK'); } catch {}
} finally {
if (req.pg) req.pg.release();
}
next?.();
}
function requireAuth(req, res, next) {
if (!req.session?.user) return res.status(401).json({ error: 'no-auth' });
next();
}
function getTenantUuid(req) {
// 1) header enviado por el front (fetchWithTenant)
const h = req.get('x-tenant-uuid');
if (h) return String(h).replace(/-/g, '');
// 2) sesión del login OIDC
const s = req.session?.user?.tenant_uuid;
if (s) return String(s).replace(/-/g, '');
throw new Error('Tenant no especificado');
}
app.get('/api/productos', requireAuth, withTenant, async (req, res, next) => {
const { rows } = await req.pg.query('SELECT * FROM productos ORDER BY id');
res.json(rows);
}, done);
app.use((req,res,next)=>{ res.locals.user = req.session?.user || null; next(); });
// ----------------------------------------------------------
// Introspección de esquema
// ----------------------------------------------------------