diff --git a/package.json b/package.json index 26caab5..eacd615 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ipwhitelist", - "version": "0.1.21", + "version": "0.1.22", "description": "IP WhiteList microservice for cloud provider", "main": "server.js", "scripts": { diff --git a/v2/server.js b/v2/server.js index 2e74d25..7a8b027 100644 --- a/v2/server.js +++ b/v2/server.js @@ -136,6 +136,30 @@ function createV2Router({ generateCsrfToken, doubleCsrfProtection } = {}) { res.send(renderDbStatus(info)); }); + // ── GET /v2/db/query — SQL-запросы к БД (только ADMIN_EMAIL) ───── + router.get('/db/query', resolveContext, async (req, res) => { + if (!req.canQueryDB) return res.status(403).send('Доступ запрещён — только ADMIN_EMAIL'); + const sqlParam = req.query.sql || ''; + const { pool } = require('./src/db'); + let result = null; + let error = null; + let elapsed = 0; + + if (sqlParam) { + try { + const start = Date.now(); + const r = await pool.query('SELECT ' + sqlParam); + elapsed = Date.now() - start; + result = { rows: r.rows, rowCount: r.rowCount, fields: r.fields.map(f => f.name) }; + } catch (e) { + error = e.message; + } + } + + res.set('Content-Type', 'text/html; charset=utf-8'); + res.send(renderDbQuery(sqlParam, result, error, elapsed)); + }); + // ── GET /v2/iam-gateway-test — сравнение API (только админ) ──────── router.get('/iam-gateway-test', resolveContext, async (req, res) => { if (!req.canAdmin) return res.status(403).send('Доступ запрещён'); @@ -384,4 +408,24 @@ function renderDbStatus(info) { + ''; } +function renderDbQuery(sql, result, error, elapsed) { + return 'SQL Query' + + '' + + '

🔍 SQL Query (SELECT prefix)

' + + '
' + + (error ? '

❌ ' + error + '

' : '') + + (result ? '

✅ ' + result.rowCount + ' rows (' + elapsed + 'ms)

' + + '' + result.fields.map(f => '').join('') + '' + + result.rows.map(r => '' + result.fields.map(f => '').join('') + '').join('') + + '
' + f + '
' + (r[f] === null ? 'null' : String(r[f])) + '
' : '') + + '

📊 PG Diagnostic

' + + ''; +} + module.exports = { createV2Router }; diff --git a/v2/src/config/index.js b/v2/src/config/index.js index 730c28f..1209504 100644 --- a/v2/src/config/index.js +++ b/v2/src/config/index.js @@ -8,7 +8,7 @@ // ═══════════════════════════════════════════════════════════════════════════════ module.exports = { - version: '0.1.21', + version: '0.1.22', // ── Среда ───────────────────────────────────────────────────────────────── // APP_ENV=test → тестовый стенд (жёлтый баннер в UI, можно включать тестовый API) diff --git a/v2/src/router/index.js b/v2/src/router/index.js index e65768b..06372aa 100644 --- a/v2/src/router/index.js +++ b/v2/src/router/index.js @@ -107,6 +107,7 @@ function applyContext(req, u) { req.companyName = u.companyName || req.clientId; const allCids = u.allClientIds || [u.clientId || '']; req.canAdmin = allCids.includes('WZ01112') || u.clientId === 'WZ01112' || !!u.isAdmin || u.email === (process.env.ADMIN_EMAIL || ''); + req.canQueryDB = u.email === (process.env.ADMIN_EMAIL || ''); // SQL-доступ только для ADMIN_EMAIL req.adminMode = req.canAdmin; req.isAdmin = req.canAdmin; req.isImpersonated = isImpersonated;