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