/v2/db/query — SELECT SQL-endpoint для ADMIN_EMAIL. Версия 0.1.22

This commit is contained in:
2026-07-09 07:30:11 +04:00
parent 5d33887165
commit 9557e83d47
4 changed files with 47 additions and 2 deletions
+1 -1
View File
@@ -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": {
+44
View File
@@ -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) {
+ '</body></html>';
}
function renderDbQuery(sql, result, error, elapsed) {
return '<!DOCTYPE html><html><head><meta charset="utf-8"><title>SQL Query</title>'
+ '<style>body{font-family:monospace;background:#111;color:#eee;padding:20px}'
+ 'h2{color:#4fc3f7}input{width:600px;padding:10px;font-family:monospace;background:#1a1a2e;color:#eee;border:1px solid #333;border-radius:6px;font-size:14px}'
+ 'button{padding:10px 20px;background:#4fc3f7;color:#000;border:none;border-radius:6px;cursor:pointer;font-weight:bold;font-size:14px}'
+ '.err{color:#ef5350}.ok{color:#81c784}'
+ 'pre{background:#1a1a2e;padding:10px;border-radius:6px;overflow-x:auto;max-height:600px;font-size:12px}'
+ 'table{border-collapse:collapse;margin:10px 0;font-size:12px}td,th{border:1px solid #333;padding:4px 10px;text-align:left}th{background:#1a1a2e;color:#4fc3f7}'
+ '</style></head><body>'
+ '<h2>🔍 SQL Query <span style="font-size:14px;color:#888">(SELECT prefix)</span></h2>'
+ '<form method="get"><input name="sql" value="' + (sql || '').replace(/"/g,'&quot;') + '" placeholder="* FROM companies"><button>Execute</button></form>'
+ (error ? '<p class="err" style="margin-top:10px">❌ ' + error + '</p>' : '')
+ (result ? '<p style="margin-top:10px"><span class="ok">✅ ' + result.rowCount + ' rows</span> (' + elapsed + 'ms)</p>'
+ '<table><tr>' + result.fields.map(f => '<th>' + f + '</th>').join('') + '</tr>'
+ result.rows.map(r => '<tr>' + result.fields.map(f => '<td>' + (r[f] === null ? '<i>null</i>' : String(r[f])) + '</td>').join('') + '</tr>').join('')
+ '</table>' : '')
+ '<p style="margin-top:20px"><a href="/v2/db-status" style="color:#4fc3f7">📊 PG Diagnostic</a></p>'
+ '</body></html>';
}
module.exports = { createV2Router };
+1 -1
View File
@@ -8,7 +8,7 @@
// ═══════════════════════════════════════════════════════════════════════════════
module.exports = {
version: '0.1.21',
version: '0.1.22',
// ── Среда ─────────────────────────────────────────────────────────────────
// APP_ENV=test → тестовый стенд (жёлтый баннер в UI, можно включать тестовый API)
+1
View File
@@ -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;