v2: export слой — GET /v2/export, агрегация CIDR

This commit is contained in:
2026-06-13 08:34:09 +04:00
parent e5815279a1
commit 30aaf8a7e4
5 changed files with 126 additions and 2 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "ipwhitelist", "name": "ipwhitelist",
"version": "0.5.95", "version": "0.5.96",
"description": "IP WhiteList microservice for cloud provider", "description": "IP WhiteList microservice for cloud provider",
"main": "server.js", "main": "server.js",
"scripts": { "scripts": {
+71
View File
@@ -677,3 +677,74 @@ WZ01112: entries=10 audit=10 dupes=False overLimit=False
Предыдущие тесты (test.sh, router): 64 теста. Предыдущие тесты (test.sh, router): 64 теста.
Общий итог: **105 тестов, все пройдены** Общий итог: **105 тестов, все пройдены**
---
## Полное тестирование всех комбинаций (0.5.95, 2026-06-13)
### Новый мок-юзер: adm
Добавлен для покрытия сценария «админ без имперсонации, много компаний»:
| Поле | Значение |
|------|---------|
| email | admin@t.ru |
| clientId | WZ01112 |
| allClientIds | WZ01112, WZ03709, WZ09999 |
| isAdmin | true |
| isImpersonated | false |
| profiles | 3 компании (Nubes, Дочка, Третья) |
### Матрица тестирования — все комбинации
| # | Сценарий | Юзер | Действия | Результат |
|---|---------|------|----------|-----------|
| 1 | 1 компания, CRUD | u1 | add×2, blocked, list=2, edit, delete, after=1 | ✅ 7/7 |
| 2 | 2 компании, switch | u2 | add WZ20002, switch→WZ20001, add, list=1, switch→WZ20002, list=1 | ✅ 6/6 |
| 3 | Имперсонация, CRUD | imp | add×2, list=2, created_by=real@t.ru, edit, delete | ✅ 5/5 |
| 4 | Имперсонация, аудит | imp | audit: imp_by=admin@t.ru, user=real@t.ru, action=DELETE | ✅ 3/3 |
| 5 | Админ, 3 компании | adm | add WZ01112, switch→WZ03709, add, switch→WZ09999, add, list=1, created_by=admin@t.ru, switch→WZ01112, list=1 | ✅ 9/9 |
| 6 | Лимит 15 | u1 | 15 записей + 16-я fail | ✅ 1/1 |
| 7 | Лимит — другая компания | u2 | add после лимита u1 | ✅ 1/1 |
| 8 | Нормализация | u1 | /22 ok, /21 fail, IPv6 fail, normalize | ✅ 4/4 |
| 9 | UserFlow u1/u2/imp | u1,u2,imp | add, list, blocked, imp | ✅ 5/5 |
| 10 | Chaos (параллельно) | — | 40 ops, 4 компании | ✅ 1/1 |
### Ключевые проверки
| Проверка | Сценарий | Результат |
|----------|---------|-----------|
| created_by = originalUserEmail при имперсонации | imp add | ✅ real@t.ru |
| created_by = email админа БЕЗ имперсонации | adm add | ✅ admin@t.ru |
| impersonated_by в аудите | imp audit | ✅ admin@t.ru |
| user_email в аудите = originalUserEmail | imp audit | ✅ real@t.ru |
| switch компаний сохраняет изоляцию | u2 switch | ✅ |
| админ видит 3 компании и переключается | adm switch×3 | ✅ |
| лимит 15 не влияет на другие компании | u1+u2 | ✅ |
### Итого: 42 тестовых сценария пройдены
| Группа | Тестов |
|--------|--------|
| u1 (1 компания) | 7 |
| u2 (2 компании) | 6 |
| imp (имперсонация) | 8 |
| adm (админ, 3 компании) | 9 |
| Лимиты | 2 |
| Нормализация | 4 |
| UserFlow | 5 |
| Chaos | 1 |
| **Всего** | **42** |
Плюс предыдущие: 105 тестов (test.sh + router + ранние).
**Общий итог: 147 тестов, все пройдены**
### Что дальше
| # | Задача | Статус |
|---|--------|--------|
| 1 | export/ — выгрузка CIDR | ❌ |
| 2 | EJS-шаблоны вместо inline HTML | ❌ |
| 3 | Auth end-to-end (KC callback) | ❌ |
| 4 | Интеграция в основной код (убрать v2_) | ❌ |
+4
View File
@@ -65,6 +65,10 @@ function createV2Router() {
router.use('/test', createTestRouter()); router.use('/test', createTestRouter());
} }
// ── /v2/export — выгрузка CIDR (публичный, без авторизации) ──────
const { createExportRouter } = require('./src/export');
router.use('/export', createExportRouter());
// ── GET /v2/logout — редирект на основной выход ───────────────────── // ── GET /v2/logout — редирект на основной выход ─────────────────────
router.get('/logout', (req, res) => res.redirect('/logout')); router.get('/logout', (req, res) => res.redirect('/logout'));
+1 -1
View File
@@ -9,7 +9,7 @@
// ═══════════════════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════════════════
module.exports = { module.exports = {
version: '0.5.95', version: '0.5.96',
// ── IAM ────────────────────────────────────────────────────────────────── // ── IAM ──────────────────────────────────────────────────────────────────
iamUrl: process.env.V2_IAM_URL || 'https://auth-api.ngcloud.ru/api/v1/auth/user', iamUrl: process.env.V2_IAM_URL || 'https://auth-api.ngcloud.ru/api/v1/auth/user',
+49
View File
@@ -0,0 +1,49 @@
// ═══════════════════════════════════════════════════════════════════════════════
// V2 — Экспорт CIDR (отдельный слой)
//
// ЭТО: выгрузка разрешённых CIDR в текстовом виде.
// НЕ: CRUD, валидация (использует db + validators).
//
// ЗАЧЕМ:
// 1. Отдать CIDR для firewall/nginx белого списка.
// 2. Агрегировать соседние CIDR в минимальный набор (экономия правил).
// 3. Поддержка фильтрации по компании (?companyId=X).
//
// ЦЕПОЧКА: GET /v2/export → q.getExportCIDRs → aggregateCIDRs → text/plain
//
// ПАРАМЕТРЫ:
// ?companyId=X — фильтр по компании (внутренний id)
// ?view=1 — показать в браузере (иначе — скачивание)
// ?filename=X — имя файла (по умолчанию white-list.txt)
// ═══════════════════════════════════════════════════════════════════════════════
const express = require('express');
const q = require('../db/queries');
const { aggregateCIDRs } = require('../validators');
function createExportRouter() {
const router = express.Router();
router.get('/', async (req, res) => {
try {
const companyId = req.query.companyId ? parseInt(req.query.companyId) : null;
const cidrs = await q.getExportCIDRs(companyId);
const aggregated = aggregateCIDRs(cidrs);
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, '_');
const disp = req.query.view === '1' ? 'inline' : 'attachment';
res.set('Content-Disposition', disp + '; filename="' + fname + '"');
res.send(text);
} catch (e) {
console.error('[v2:export] Error:', e);
res.status(500).send('Ошибка экспорта');
}
});
return router;
}
module.exports = { createExportRouter };