Export: убран filename из Content-Disposition для диалога сохранения
Версия: 0.1.13 Документация: db-query-endpoint.md
This commit is contained in:
@@ -6,6 +6,7 @@ __pycache__/
|
||||
.DS_Store
|
||||
.vscode/
|
||||
.idea/
|
||||
*.har
|
||||
|
||||
# Креды, токены, конфиги
|
||||
token*.*
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
# Endpoint SQL-запросов к БД (`/v2/db/query`)
|
||||
|
||||
> Создан: 2026-07-08
|
||||
|
||||
## Назначение
|
||||
|
||||
Дать DevOps-инженеру (одному человеку, заданному через `ADMIN_EMAIL`) возможность выполнять SQL-запросы к БД напрямую из браузера — без psql, без kubectl, без внешнего IP.
|
||||
|
||||
## Доступ
|
||||
|
||||
- Только один пользователь: `req.email === ADMIN_EMAIL`
|
||||
- Обычный юзер сервиса (проходит SSO как все)
|
||||
- Пример: `ADMIN_EMAIL=atazet@narod.ru`
|
||||
|
||||
## Адрес
|
||||
|
||||
```
|
||||
GET /v2/db/query?sql=<часть запроса после SELECT>
|
||||
```
|
||||
|
||||
## Как работает
|
||||
|
||||
1. Пользователь заходит в сервис через SSO (любой URL, например `/v2/app`)
|
||||
2. Меняет в адресной строке путь на `/v2/db/query?sql=...`
|
||||
3. Сервер:
|
||||
- Проверяет сессию (resolveContext)
|
||||
- Проверяет что `req.email === ADMIN_EMAIL`
|
||||
- Добавляет префикс `SELECT` перед вводом пользователя
|
||||
- Выполняет: `pool.query('SELECT ' + userInput)`
|
||||
- Возвращает JSON
|
||||
|
||||
## Примеры
|
||||
|
||||
| Ввод в браузере | Что выполняется |
|
||||
|---|---|
|
||||
| `/v2/db/query?sql=* FROM _migrations` | `SELECT * FROM _migrations` |
|
||||
| `/v2/db/query?sql=count(*) FROM companies` | `SELECT count(*) FROM companies` |
|
||||
| `/v2/db/query?sql=* FROM whitelist_entries LIMIT 10` | `SELECT * FROM whitelist_entries LIMIT 10` |
|
||||
| `/v2/db/query?sql=* FROM audit_log ORDER BY created_at DESC LIMIT 5` | `SELECT * FROM audit_log ORDER BY created_at DESC LIMIT 5` |
|
||||
|
||||
## Безопасность
|
||||
|
||||
1. **SELECT префикс** — жёстко добавляется `SELECT` перед вводом пользователя. DROP/INSERT/UPDATE/DELETE не работают:
|
||||
```
|
||||
/v2/db/query?sql=DROP TABLE companies
|
||||
→ SELECT DROP TABLE companies (ошибка SQL, не выполнится)
|
||||
```
|
||||
|
||||
2. **Одиночные запросы** — драйвер `pg` не поддерживает множественные запросы в одном `query()`. Попытка `1; DROP TABLE` не сработает.
|
||||
|
||||
3. **Доступ по email** — только один человек, заданный в `ADMIN_EMAIL`.
|
||||
|
||||
4. **Rate limiter** — endpoint защищён лимитером (как mutationLimiter).
|
||||
|
||||
## Ответ
|
||||
|
||||
```json
|
||||
{
|
||||
"rows": [ ... ],
|
||||
"rowCount": 5,
|
||||
"fields": ["id", "filename", "applied_at"]
|
||||
}
|
||||
```
|
||||
|
||||
## Реализация
|
||||
|
||||
- Файл: `v2/src/db/query.js`
|
||||
- Middleware: `resolveContext` + проверка `req.canQueryDB`
|
||||
- Роутер монтируется в `v2/server.js` на `/db`
|
||||
- Флаг `req.canQueryDB` выставляется в `v2/src/router/index.js` (applyContext)
|
||||
|
||||
## Ограничения
|
||||
|
||||
- Только SELECT (через префикс)
|
||||
- Нет GUI — только JSON в браузере
|
||||
- SQL в URL (логируется в access_log nginx/ingress — это нормально для devops-инструмента)
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ipwhitelist",
|
||||
"version": "0.1.12",
|
||||
"version": "0.1.13",
|
||||
"description": "IP WhiteList microservice for cloud provider",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
module.exports = {
|
||||
version: '0.1.12',
|
||||
version: '0.1.13',
|
||||
|
||||
// ── Среда ─────────────────────────────────────────────────────────────────
|
||||
// APP_ENV=test → тестовый стенд (жёлтый баннер в UI, можно включать тестовый API)
|
||||
|
||||
@@ -45,11 +45,10 @@ function createExportRouter() {
|
||||
return res.send(text);
|
||||
}
|
||||
|
||||
// Скачивание (по умолчанию)
|
||||
// Скачивание (по умолчанию) — без filename чтобы браузер показал диалог сохранения
|
||||
const text = aggregated.join('\n') + (aggregated.length ? '\n' : '');
|
||||
res.set('Content-Type', 'text/plain; charset=utf-8');
|
||||
const fname = (req.query.filename || 'white-list.txt').replace(/[^\w\-_. ]/g, '_');
|
||||
res.set('Content-Disposition', 'attachment; filename="' + fname + '"');
|
||||
res.set('Content-Disposition', 'attachment');
|
||||
res.send(text);
|
||||
} catch (e) {
|
||||
console.error('[v2:export] Error:', e);
|
||||
|
||||
Reference in New Issue
Block a user