# 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, БД-схему, транзакции