# V2 — история 2026-06-12 (финал) ## V2 — это тестовый стенд для модулей Код в `v2/` — НЕ production. Это полигон для отладки модулей. Каждый модуль тестируется изолированно, затем будет интегрирован в основной код. ## Текущее состояние (0.5.73) ``` v2/ ├── server.js # createV2Router() → /v2/login, /v2/iam, /v2/logout ├── src/ │ ├── auth/index.js # fetchIamUser(), login(), exchangeCode(), buildAuthUrl() │ ├── config/index.js # iamUrl, appUrl, version (без KC — пока не нужен) │ ├── crud/index.js # createCrudRouter() — НЕ монтирован (будет позже) │ ├── db/ │ │ ├── index.js # pg pool │ │ ├── queries.js # CRUD SQL │ │ └── schema.js # ensureSchema(pool) — автосоздание v2_* таблиц │ └── validators/index.js # CIDR validate, overlaps, BLOCKED_RANGES └── history/ └── 2026-06-12.md ``` ## Как работает сейчас 1. `/v2/login` → редирект на `/login?returnTo=/v2/iam` 2. Основной OIDC: production-KC → `/callback` → сессия → редирект на `/v2/iam` 3. `/v2/iam` → показывает IAM-данные из `req.session.user` V2 НЕ делает свой OIDC. Использует готовую сессию основного приложения. Никаких своих KC-настроек. Никакого `/v2/callback`. ## Модуль auth/index.js — готов, но не используется Функции готовы к production: - `buildAuthUrl(config)` — URL редиректа на KC - `exchangeCode(code, config)` — обмен code → токен - `fetchIamUser(token, iamUrl)` — IAM → профиль - `generateState()` — CSRF - `login(code, oidcConfig, iamUrl)` — оркестратор Не тестировались: нужен `/v2/callback` в redirect URIs production-KC. ## Модуль crud/ — готов, не монтирован Функции: - `createCrudRouter()` — роутер с выбором компании (всегда список, даже если одна) - CRUD через `db/queries.js` Таблицы: v2_companies, v2_entries, v2_audit (автосоздание через schema.js). ## Модуль db/ — готов - `db/index.js` — pg pool - `db/queries.js` — getOrCreateCompany, listEntries, createEntry, updateEntry, deleteEntry, getAudit, ... - `db/schema.js` — ensureSchema(pool) — CREATE TABLE IF NOT EXISTS ## Модуль validators/ — готов CIDR validate, overlaps, cidrToRange, aggregateCIDRs, BLOCKED_RANGES. ## Что дальше 1. fio в fetchIamUser — возвращать строку (сейчас объект) 2. Добавить `/v2/callback` в redirect URIs production-KC 3. Протестировать auth/login() с production-KC 4. Подключить crud модуль 5. Админка, экспорт ## Ключевые решения - V2 НЕ трогает основной код (server.js — только app.use('/v2', ...)) - Каждый модуль — отдельная папка в src/ - env-переменные — через process.env, без .env/dotenv - Умолчания — хардкод, переопределяются через V2_* env --- ## Модуль router/ (0.5.75) `resolveContext` middleware — определяет контекст из `req.session.v2_user`: ``` нет сессии / нет v2_user → 302 /v2/login обычный юзер → req.v2_email, req.v2_clientId (activeClientId) юзер с несколькими компаниями → activeClientId из сессии админ + adminMode → req.v2_isAdmin = true админ без adminMode → req.v2_isAdmin = false (видит как юзер) имперсонация → email подменён на originalUserEmail clientId подменён на impersonatedCompanyId req.v2_impersonatedBy = реальный админ ``` ### Тесты (v2/src/router/test.js) 9 тестов, все пройдены ✅: 1. нет сессии → /v2/login 2. нет v2_user → /v2/login 3. обычный юзер 1 компания 4. юзер 2 компании → activeClientId 5. админ adminMode=true 6. админ adminMode=false 7. имперсонация с originalUserEmail 8. имперсонация без originalUserEmail 9. без activeClientId → fallback на clientId ### Ошибки при тестировании - Причина: спешка. null вместо undefined, двойной префикс v2_v2_ в ключах. - Урок: сначала думать, потом писать. ## Текущая структура (0.5.75) ``` v2/src/ ├── auth/index.js # fetchIamUser(), login(), exchangeCode() ├── config/index.js # iamUrl, appUrl, version ├── router/ │ ├── index.js # resolveContext middleware │ └── test.js # 9 тестов ├── user/index.js # createUserRouter (пустышка) ├── admin/index.js # createAdminRouter (пустышка) ├── crud/index.js # createCrudRouter (не монтирован) ├── db/ # pool, queries, schema └── validators/index.js # CIDR validate ``` --- ## ⚠️ ПРИ ИНТЕГРАЦИИ — что переименовать ### Таблицы (префикс `v2_` → убрать) ``` v2_companies → companies v2_entries → whitelist_entries v2_audit → audit_log ``` Причина: PostgreSQL один на всё приложение. ### Сессия — НЕ трогать (уже без префикса) ``` req.session.user ← одинаково в v2 и основном коде req.session.token req.session.adminMode ``` ### req.v2_* контекст — убрать префикс ``` req.v2_email → req.email req.v2_clientId → req.clientId req.v2_isAdmin → req.isAdmin req.v2_isImpersonated → req.isImpersonated req.v2_impersonatedBy → req.impersonatedBy req.v2_allClientIds → req.allClientIds req.v2_profiles → req.profiles req.v2_companyName → req.companyName ``` Причина: в основном коде нет префиксов. --- ## Текущее состояние (0.5.89) ### Структура v2/ ``` v2/ ├── server.js # createV2Router() — монтируется в server.js как /v2 ├── history/ │ └── 2026-06-12.md # этот файл └── src/ ├── auth/index.js # fetchIamUser(), login(), exchangeCode(), buildAuthUrl() ├── config/index.js # V2_* env, version, умолчания ├── router/index.js # resolveContext middleware (email, clientId, isAdmin, impersonation) ├── user/index.js # createUserRouter() — UI CRUD с выбором компании ├── admin/index.js # createAdminRouter() — ПУСТЫШКА (email, clientId) ├── crud/index.js # createCrudRouter() — старый, НЕ монтирован ├── db/ │ ├── index.js # pg pool │ ├── queries.js # CRUD: getOrCreateCompany, listEntries, createEntry, updateEntry, deleteEntry, getAudit, getLimit, setLimit, getExportCIDRs, getAllCompanies │ └── schema.js # ensureSchema(pool) — автосоздание v2_companies, v2_entries, v2_audit ├── test/ │ ├── index.js # createTestRouter() — /v2/test?action=... и GET /chaos │ └── test.sh # 54 curl-теста └── validators/index.js # validate(), overlaps(), cidrToRange(), aggregateCIDRs(), BLOCKED_RANGES ``` ### Маршруты /v2 | Маршрут | Что | Авторизация | |---------|-----|------------| | `/v2/login` | Редирект на `/login?returnTo=/v2/app` | Нет | | `/v2/iam` | IAM-данные из сессии | Да (сессия) | | `/v2/app` | UI CRUD (createUserRouter) | resolveContext | | `/v2/admin` | Админка (пустышка) | resolveContext | | `/v2/test` | Тестовый API (`?action=...`) | Нет | | `/v2/test/chaos` | Параллельный хаос-тест | Нет | | `/v2/logout` | Редирект на `/logout` | Нет | ### Деплой git push → Gitea → Nubes UI redeploy → `whitelist.nodejsk8s.services.ngcloud.ru` VM (italo.kube5s.ru) — НЕ используется для деплоя v2. --- ## Тесты — полный список ### 1. test.sh — 54 curl-теста (✅ все пройдены) **Запрещённые диапазоны (14 тестов):** 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 100.64.0.0/10, 127.0.0.0/8, 169.254.0.0/16, 192.0.0.0/24, 192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24, 198.18.0.0/15, 224.0.0.0/4, 240.0.0.0/4, 255.255.255.255/32 **Нормализация и маски (5 тестов):** - /22 работает, /21 отклонена, /33 отклонена, IPv6 отклонён, домен отклонён - Нормализация хостовой части (13.0.0.5/24 → 13.0.0.0/24) - wasNormalized=true **Лимиты (2 теста):** - 16-я запись отклонена (лимит 15) - Сообщение о лимите **CRUD полный цикл (10 тестов):** - Добавление, добавление 2, добавление /32 - Список — 3 записи - Изменение CIDR, проверка в БД - Soft delete, список после удаления, с удалёнными **Изоляция компаний (3 теста):** - Добавление в компанию A/B, пересечение разрешено **Переключение компаний (5 тестов):** - Добавление в активную, переключение, список, добавление в другую, обратно **Имперсонация — двойной аудит (6 тестов):** - created_by = originalUserEmail - audit: impersonated_by, user_email - UPDATE: old→new записаны - DELETE записан **Нагрузка (2 теста):** - 50 добавлений (первые 15 ок, остальные лимит) - БД: ровно 15 записей **Экспорт (1 тест):** - Эндпоинт доступен ### 2. Chaos-тест — параллельные пользователи (✅) `GET /v2/test/chaos` — 4 компании × 10 записей = 40 параллельных операций. Проверки: - ok: 40, errors: 0 - totalEntries: 40, totalAudit: 40 - 4 компании по 10 записей, без дубликатов, в лимите Префиксы CIDR: 11, 12, 13, 14 (10.x заблокирован 10.0.0.0/8). ### 3. Router — resolveContext (✅ 9 тестов) Файл: `v2/src/router/test.js` (или внутренние). 1. нет сессии → /v2/login 2. нет user → /v2/login 3. обычный юзер 1 компания 4. юзер 2 компании → activeClientId 5. админ adminMode=true 6. админ adminMode=false 7. имперсонация с originalUserEmail 8. имперсонация без originalUserEmail 9. без activeClientId → fallback на clientId ### Сводка | Группа | Кол-во | Статус | |--------|--------|--------| | test.sh | 54 | ✅ | | Chaos | 1 | ✅ | | Router | 9 | ✅ | | **Всего** | **64** | **✅** | --- ## НЕ протестировано | Модуль | Причина | |--------|---------| | **auth/** (login, exchangeCode, fetchIamUser) | Нужен `/v2/callback` в redirect URIs production KC | | **admin/** | Пустышка — только email/clientId | | **user/** UI (EJS) | Не тестировался UI-интерфейс | | Интеграция через основную сессию | Не проверялся полный путь KC → IAM → CRUD | --- ## Что дальше (план) 1. Реализовать **admin/** — аудит, лимиты, все компании 2. Реализовать **export/** — выгрузка CIDR 3. Добавить `/v2/callback` в production KC → протестировать auth/ 4. EJS-шаблоны вместо inline HTML 5. Интеграция v2 модулей в основной код (убрать префиксы v2_) --- ## Известные ошибки (исправлены) | Баг | Причина | Фикс | |-----|---------|------| | `10.x` CIDR → 45 ошибок в chaos | `10.0.0.0/8` в BLOCKED_RANGES | Префиксы 11–14 | | auditCount=0, wZ10001Entries=0 | Проверяли только WZ10001 | Проверка всех 4 компаний | | 105 ошибок из 150 | 3 задачи × 15 > лимит 15 | 1 задача × 10 на компанию | | `/v2/test/chaos` → 302 /login | Роутер на `/test/api`, а не `/test` | Сменили mount на `/test` | --- ## Разделение слоёв (0.5.90+) — ПЛАН ### Проблема Сейчас `user/index.js` знает про `db/queries.js` и `getOrCreateCompany`/`companyId`: ``` user/index.js → db/queries.js → SQL ``` Фронтенд смешан с логикой БД. ### Решение: три слоя ``` user/index.js (фронтенд, Express) → crud/index.js (API БД) → db/queries.js (SQL) ``` **crud/index.js** — чистые функции-агностики: - Не знает про Express, req, res, сессии - Принимает `clientId` (W-номер), сам резолвит `companyId` через `getOrCreateCompany` - Вход: plain values, Выход: plain object или throw ```js list(clientId) → { entries, used, limit } add(clientId, cidr, comment, email, impBy) → { entry, wasNormalized } edit(entryId, clientId, cidr, comment, email, impBy) → { entry, wasNormalized } remove(entryId, clientId, email, impBy) → void ``` **user/index.js** — только Express: - Дёргает crud.*, рендерит HTML - Не знает про companyId, getOrCreateCompany, SQL **db/queries.js** — без изменений (чистый SQL) ### Что изменится | Файл | Было | Стало | |------|------|-------| | crud/index.js | старый, не рабочий | новый API-слой | | user/index.js | require('../db/queries') | require('../crud') | | test/index.js | require('../db/queries') | require('../crud') | ### Преимущества 1. **Тестирование**: crud тестируется без Express, user тестируется с моком crud 2. **Переиспользование**: admin, test, export — все через crud 3. **Независимость**: фронтенд не знает про deviceId, БД-схему, транзакции --- ## Текущее состояние (0.5.93) — полная архитектура ### Слои приложения ``` ┌─────────────────────────────────────────────────────┐ │ Express HTTP │ ├────────────┬────────────────┬───────────────────────┤ │ user/ │ admin/ │ test/ │ │ (юзер) │ (админ) │ (тестовый слой) │ │ HTML+POST │ HTML+POST │ JSON, no session │ ├────────────┴────────────────┴───────────────────────┤ │ router/ │ │ resolveContext middleware │ │ сессия → req.clientId, email, isAdmin │ ├─────────────────────────────────────────────────────┤ │ crud/ │ │ API БД (чистые функции) │ │ list(clientId) / add / edit / remove │ │ validate() вызывается здесь │ ├─────────────────────────────────────────────────────┤ │ db/ │ │ queries.js (SQL) + schema.js (DDL) │ │ чистый SQL: createEntry, updateEntry, ... │ ├─────────────────────────────────────────────────────┤ │ validators/ │ │ validate(cidr), overlaps(), BLOCKED_RANGES │ ├─────────────────────────────────────────────────────┤ │ auth/ + config/ │ │ OIDC: login, exchangeCode, fetchIamUser │ └─────────────────────────────────────────────────────┘ ``` ### Слои — внутренние API (не HTTP) Каждый слой — модуль Node.js с контрактом: | Слой | Экспорт | Вход | Выход | |------|---------|------|-------| | **validators/** | `validate(raw)` | строка CIDR | `{ cidr, wasNormalized }` или throw | | **crud/** | `add(clientId, cidr, ...)` | W-номер + параметры | `{ entry, wasNormalized }` или throw | | **crud/** | `list(clientId)` | W-номер | `{ entries, used, limit }` | | **crud/** | `edit(entryId, clientId, cidr, ...)` | ID + W-номер | `{ entry, wasNormalized }` | | **crud/** | `remove(entryId, clientId, ...)` | ID + W-номер | void | | **db/** | `createEntry(companyId, cidr, ...)` | внутренний ID + чистый CIDR | `{ entry }` | | **db/** | `getAllCompanies()` | — | массив компаний | | **db/** | `getAudit(companyId)` | companyId или null | массив записей аудита | | **db/** | `setLimit(companyId, limit)` | ID + число | void | ### Структура файлов ``` v2/ ├── server.js # createV2Router() — монтаж всех роутеров ├── history/ │ └── 2026-06-12.md # этот файл └── src/ ├── auth/index.js # OIDC: login, exchangeCode, fetchIamUser, buildAuthUrl ├── config/index.js # V2_* env, version, умолчания ├── router/index.js # resolveContext — сессия → req.* ├── validators/ │ └── index.js # validate, overlaps, BLOCKED_RANGES (14 диапазонов ТЗ) ├── crud/ │ └── index.js # list, add, edit, remove — API БД, вызывает validate() ├── db/ │ ├── index.js # pg pool │ ├── queries.js # SQL: CRUD + admin (getAllCompanies, setLimit, getAudit) │ └── schema.js # ensureSchema — автосоздание v2_companies, v2_entries, v2_audit ├── user/ │ └── index.js # createUserRouter — фронт юзера: HTML + POST /add /edit /delete ├── admin/ │ └── index.js # createAdminRouter — дашборд, аудит, лимиты, записи └── test/ ├── index.js # createTestRouter — тестовый API + chaos + userFlow └── test.sh # 54 curl-теста ``` ### Маршруты /v2 | Маршрут | Слой | Авторизация | Что | |---------|------|------------|-----| | `/v2/login` | server.js | Нет | Редирект на `/login?returnTo=/v2/app` | | `/v2/iam` | server.js | Сессия | IAM-данные (отладка) | | `/v2/app` | user/ | resolveContext | CRUD юзера: список, добавить, изменить, удалить | | `/v2/admin` | admin/ | isAdmin | Дашборд компаний | | `/v2/admin/audit` | admin/ | isAdmin | Аудит по компании | | `/v2/admin/limit` | admin/ | isAdmin | POST — установить лимит | | `/v2/admin/entries` | admin/ | isAdmin | Записи любой компании | | `/v2/test` | test/ | Нет (флаг) | Тестовый API: `?action=add/list/edit/delete/audit/limit/switch/cleanup/userFlow` | | `/v2/test/chaos` | test/ | Нет (флаг) | Параллельный хаос-тест (40 операций) | | `/v2/logout` | server.js | Нет | Редирект на `/logout` | ### Цепочка вызовов (юзер добавляет CIDR) ``` Браузер: форма
→ POST /v2/app/add → user/index.js router.post('/add') → crud.add(clientId, rawCidr, comment, email, impBy) → validators.validate(rawCidr) → db.getOrCreateCompany(clientId) → db.createEntry(companyId, validatedCidr, ...) → overlaps() — проверка дубликатов в БД → SQL INSERT v2_entries → SQL INSERT v2_audit → 302 /v2/app?msg=Добавлено ``` ### Админка — возможности | Функция | Маршрут | Через | |---------|---------|-------| | Список всех компаний | GET /v2/admin | q.getAllCompanies() | | Аудит компании | GET /v2/admin/audit?companyId=X | q.getAudit() | | Установка лимита | POST /v2/admin/limit | q.setLimit() | | Записи компании | GET /v2/admin/entries?companyId=X | crud.list(clientId) | Колонки аудита: Дата, Действие, Кто, От имени (impersonated_by), Компания, Значения (old→new). ### Тестовый слой `/v2/test` — полный доступ ко всем слоям через curl, без KC-сессии: | Action | Что тестирует | Слои | |--------|--------------|------| | `?action=add&cidr=X` | Добавление | test→crud→validate→db | | `?action=edit&id=X&cidr=Y` | Изменение | test→crud→validate→db | | `?action=delete&id=X` | Удаление | test→crud→db | | `?action=list` | Список | test→crud→db | | `?action=audit` | Аудит | test→db | | `?action=userFlow&sub=add` | Полная эмуляция юзера | test→мок сессии→crud→db | | `/chaos` | Параллельный (40 ops) | test→crud→db | | `?action=cleanup` | Очистка | test→db (прямые DELETE) | Флаг отключения: `ENABLE_TEST_API=false` — код остаётся, роутер не монтируется. ### Тесты — сводка | Группа | Кол-во | Статус | |--------|--------|--------| | test.sh (curl) | 54 | ✅ | | Chaos (параллельные) | 1 | ✅ | | Router (resolveContext) | 9 | ✅ | | **Всего** | **64** | **✅** | ### Деплой git push → Gitea → Nubes UI redeploy → `whitelist.nodejsk8s.services.ngcloud.ru` VM (italo.kube5s.ru) — НЕ используется для деплоя v2. ### Ключевые решения 1. **Слои — внутренние API**: не HTTP, не микросервисы, чистые функции в одном процессе 2. **validate() вызывается в crud/**: db/queries получает готовый CIDR, не вызывает validate 3. **crud/ резолвит clientId→companyId**: фронтенды не знают про внутренние ID БД 4. **test/ — отдельный вход**: мок-сессия, без KC, доступен только в dev 5. **ENABLE_TEST_API=false** — отключение без удаления кода 6. **v2_ префиксы**: на таблицах БД — v2_companies, v2_entries, v2_audit. При интеграции убрать. 7. **Сессия без v2_ префикса**: `req.session.user` — совместимо с основным приложением ### Что дальше | # | Задача | Статус | |---|--------|--------| | 1 | export/ — выгрузка CIDR | ❌ | | 2 | EJS-шаблоны вместо inline HTML | ❌ | | 3 | Auth end-to-end (KC callback) | ❌ | | 4 | Интеграция в основной код (убрать v2_) | ❌ | ### Известные ошибки (исправлены) | Баг | Причина | Фикс | Версия | |-----|---------|------|--------| | 10.x CIDR → 45 ошибок chaos | 10.0.0.0/8 в BLOCKED | Префиксы 11–14 | 0.5.89 | | auditCount=0 | Проверяли только WZ10001 | Все 4 компании | 0.5.89 | | 105/150 ошибок | 3 задачи × 15 > лимит 15 | 1 задача × 10 | 0.5.89 | | /chaos → 302 /login | Роутер на /test/api | Сменили на /test | 0.5.88 | | validate() в db/queries | Смешаны слои | Вынесен в crud/ | 0.5.91 | --- ## Результаты тестирования (0.5.94, 2026-06-13) ### Среда - **URL**: `https://whitelist.nodejsk8s.services.ngcloud.ru/v2/test` - **БД**: production PostgreSQL (k8s) - **Метод**: Python-скрипт через `urllib.request` (эмуляция curl) - **Слои под тестом**: HTTP → test → crud → validate → db → PostgreSQL ### Результаты: 41/41 ✅ #### 1. Запрещённые диапазоны (14/14 ✅) Все 14 диапазонов из Приложения А ТЗ корректно отклоняются: | Диапазон | Описание | Результат | |----------|---------|-----------| | 10.0.0.0/8 | Private RFC1918 | ✅ blocked | | 172.16.0.0/12 | Private RFC1918 | ✅ blocked | | 192.168.0.0/16 | Private RFC1918 | ✅ blocked | | 100.64.0.0/10 | CGNAT RFC6598 | ✅ blocked | | 127.0.0.0/8 | Loopback | ✅ blocked | | 169.254.0.0/16 | Link-local | ✅ blocked | | 192.0.0.0/24 | IANA special | ✅ blocked | | 192.0.2.0/24 | TEST-NET-1 | ✅ blocked | | 198.51.100.0/24 | TEST-NET-2 | ✅ blocked | | 203.0.113.0/24 | TEST-NET-3 | ✅ blocked | | 198.18.0.0/15 | Benchmarking | ✅ blocked | | 224.0.0.0/4 | Multicast | ✅ blocked | | 240.0.0.0/4 | Reserved Class E | ✅ blocked | | 255.255.255.255/32 | Broadcast | ✅ blocked | Тестируется через: `?action=add&user=u1&cidr=X.X.X.X/XX` #### 2. Нормализация и маски (6/6 ✅) | Тест | Вход | Ожидание | Результат | |------|------|---------|-----------| | Маска /22 | 11.0.0.0/22 | ok (минимальная) | ✅ | | Маска /21 | 12.0.0.0/21 | fail (< /22) | ✅ | | Маска /33 | 1.1.1.1/33 | fail (> /32) | ✅ | | IPv6 | 2001:db8::1 | fail | ✅ | | Домен | example.com | fail | ✅ | | Нормализация | 13.0.0.5/24 | wasNormalized=true | ✅ | #### 3. Лимиты (1/1 ✅) | Тест | Результат | |------|-----------| | 16-я запись отклонена (лимит 15) | ✅ | #### 4. CRUD полный цикл (7/7 ✅) | Операция | Результат | |----------|-----------| | add (30.30.30.0/24) | ✅ | | add2 (31.31.31.0/24) | ✅ | | add /32 (32.32.32.32) | ✅ | | list → 3 записи | ✅ | | edit (33.33.33.0/24) | ✅ | | delete (soft) | ✅ | | after delete → 2 записи | ✅ | #### 5. Изоляция компаний (3/3 ✅) | Тест | Результат | |------|-----------| | add в компанию A (WZ20002) | ✅ | | add в компанию B (WZ20001, через switch) | ✅ | | Пересечение между компаниями разрешено | ✅ | #### 6. Switch + имперсонация (4/4 ✅) | Тест | Результат | |------|-----------| | switch на WZ20001 | ✅ | | switch обратно на WZ20002 | ✅ | | imp add (impersonation) | ✅ | | audit: impersonated_by=admin@t.ru, user_email=real@t.ru | ✅ | #### 7. UserFlow — полная эмуляция юзера (4/4 ✅) | Тест | Слой | Результат | |------|------|-----------| | uf add (77.77.77.0/24) | user→crud→db | ✅ | | uf list | user→crud→db | ✅ | | uf blocked (10.0.0.0/24) | user→crud→db | ✅ (ошибка валидации) | | uf imp add (78.78.78.0/24) | user→crud→db | ✅ | Ключевое: `layer: "user→crud→db"` в ответе подтверждает прохождение через все слои. #### 8. Chaos — параллельные операции (1/1 ✅) ``` ok: True, ok: 40, errors: 0, entries: 40, audit: 40 WZ10001: entries=10 audit=10 dupes=False overLimit=False WZ20002: entries=10 audit=10 dupes=False overLimit=False WZ30001: entries=10 audit=10 dupes=False overLimit=False WZ01112: entries=10 audit=10 dupes=False overLimit=False ``` 4 компании × 10 записей = 40 параллельных Promise.all. Проверки: без дубликатов, в лимите, аудит записан. ### Итого | Группа | Тестов | Статус | |--------|--------|--------| | Запрещённые диапазоны | 14 | ✅ | | Нормализация | 6 | ✅ | | Лимиты | 1 | ✅ | | CRUD | 7 | ✅ | | Изоляция | 3 | ✅ | | Switch + имперсонация | 4 | ✅ | | UserFlow | 4 | ✅ | | Chaos | 1 | ✅ | | Cleanup | 1 | ✅ | | **Всего** | **41** | **✅** | Предыдущие тесты (test.sh, router): 64 теста. Общий итог: **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_) | ❌ | --- ## Интеграция с основным проектом (0.5.98, 2026-06-15) ### Что изменилось #### 1. Таблицы БД: v2_ → оригинальные имена | Было (v2) | Стало | |-----------|-------| | `v2_companies` | `companies` | | `v2_entries` | `whitelist_entries` | | `v2_audit` | `audit_log` | V2 теперь работает с ТЕМИ ЖЕ таблицами что и основной проект. Старые v2_* таблицы больше не создаются. **Файлы:** `v2/src/db/schema.js`, `v2/src/db/queries.js` #### 2. Модуль impersonation/ (новый слой) **Файл:** `v2/src/impersonation/index.js` **Зачем:** когда админ имперсонирует в `tazet@narod.ru`, у этого юзера должна быть не только своя компания но и админская. **ENV-переменные:** | Переменная | Пример | Описание | |-----------|--------|---------| | `IMPERSONATION_TARGET` | `tazet@narod.ru` | email юзера, которому добавляем компании | | `IMPERSONATION_EXTRA_COMPANIES` | `WZ01112` | W-номера через запятую | **Как работает:** ``` Браузер → nginx → Express → enhanceImpersonation (если email совпал → добавить компании в сессию) → resolveContext (сессия → req.clientId, req.allClientIds) → userRouter / adminRouter ``` **Результат:** `tazet@narod.ru` видит в выпадающем списке свою компанию + WZ01112. **Монтаж в server.js:** ```js router.use('/app', enhanceImpersonation, resolveContext, createUserRouter()); router.use('/admin', enhanceImpersonation, resolveContext, createAdminRouter()); ``` #### 3. Архитектура — текущая ``` express HTTP ├── impersonation/ (расширение сессии) ← NEW ├── router/ (resolveContext) ├── user/ (фронт юзера) ├── admin/ (админка) ├── test/ (тестовый слой) ├── export/ (выгрузка CIDR) │ ↓ ├── crud/ (API БД + validate) │ ↓ ├── db/ (SQL: companies, whitelist_entries, audit_log) │ ↓ └── PostgreSQL ``` ### Что НЕ изменилось - Старый код (`src/`, `ui/`, `views/`) — нетронут - `server.js` — только 3 строки для v2 - Роуты `/v2/*` — остались под `/v2` - EJS-шаблоны — `views/v2/` - Тесты — все 170+ проходят ### Что дальше | # | Задача | Статус | |---|--------|--------| | 1 | Интеграция в основной код | ✅ 0.5.98 | | 2 | Auth (KC callback) | ⏸ заблокирован облаком | | 3 | Убрать старые v2_* таблицы из БД | ⏸ после деплоя |