From 608560eb6481764032eb0293401e340df8c5449e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Sat, 30 May 2026 14:42:52 +0300 Subject: [PATCH] =?UTF-8?q?docs:=20=D0=B0=D0=BA=D1=82=D1=83=D0=B0=D0=BB?= =?UTF-8?q?=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20(2026-05-30)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deprecated (описывали состояние до рефакторинга): [DEPRECATED]-plan.md [DEPRECATED]-analysis-2026-05-30.md [DEPRECATED]-auth-architecture.md Новые/обновлённые: architecture.md — текущее устройство: стек, модули, auth-схема, env, маршруты plan.md — только pending задачи (блокеры KK, деплой, Redis, пагинация) questions.md — закрытые вопросы отмечены, открытые: KK creds + admin claim + /export IP --- docs/WhiteIPlist.txt | 36 ++++ ...md => [DEPRECATED]-analysis-2026-05-30.md} | 0 ...e.md => [DEPRECATED]-auth-architecture.md} | 0 docs/[DEPRECATED]-plan.md | 107 +++++++++++ docs/architecture.md | 169 ++++++++++++++++++ docs/plan.md | 145 +++++++-------- docs/questions.md | 92 +++++----- 7 files changed, 420 insertions(+), 129 deletions(-) rename docs/{analysis-2026-05-30.md => [DEPRECATED]-analysis-2026-05-30.md} (100%) rename docs/{auth-architecture.md => [DEPRECATED]-auth-architecture.md} (100%) create mode 100644 docs/[DEPRECATED]-plan.md create mode 100644 docs/architecture.md diff --git a/docs/WhiteIPlist.txt b/docs/WhiteIPlist.txt index ca5c6f4..cbe6983 100644 --- a/docs/WhiteIPlist.txt +++ b/docs/WhiteIPlist.txt @@ -2,6 +2,42 @@ Микросервис управления доверенными адресами клиентов Self-service портал для указания клиентами доверенных IPv4-адресов и подсетей, исключаемых из блокировки на стороне облачного провайдера во время DDoS-атак + +─────────────────────────────────────────────────────────────── +КРАТКОЕ ОПИСАНИЕ (пояснение к реализации) +─────────────────────────────────────────────────────────────── +Сервис даёт клиентам облачного провайдера личный кабинет, где они сами указывают +свои доверенные IPv4-адреса и подсети. Эти адреса провайдер не блокирует во время +DDoS-атак — так легитимный трафик клиента не попадает под ложные срабатывания +фильтрации. + +Что реализуется: + +Личный кабинет клиента. Клиент входит через привычную авторизацию (Keycloak), +видит свой список доверенных адресов и управляет им сам: добавляет, редактирует, +удаляет записи с комментариями. Если пользователь работает с несколькими +компаниями — переключается между ними. + +Проверка вводимых данных. Форма принимает только корректные IPv4-адреса и подсети, +отклоняет «серые» и служебные диапазоны, не допускает дубликатов и пересечений +внутри одной компании. + +Ограничение по количеству. На компанию по умолчанию 15 записей. Лимит +настраивается глобально, а для отдельной компании администратор может поднять или +опустить его индивидуально. + +Режим администратора (сетевые инженеры провайдера). Единое окно, где видны записи +всех компаний, с фильтрами и доступом к истории изменений. Администратор управляет +лимитами и при необходимости любыми записями. + +История изменений (аудит). Каждое создание, изменение и удаление фиксируется: кто, +когда, что именно изменил. Удаление — логическое, данные физически сохраняются. + +Выдача для систем фильтрации. Отдельный адрес, по которому системы защиты +автоматически забирают итоговый сводный список всех доверенных адресов (одним +txt-файлом, по строке на запись). Адреса при этом схлопываются в компактный общий +перечень. +─────────────────────────────────────────────────────────────── 1. Назначение и цели 1.1. Назначение Микросервис предоставляет клиентам облачного провайдера web-интерфейс для самостоятельного управления списком доверенных IPv4-адресов и подсетей. Записи из этого списка исключаются из автоматической блокировки сетевого взаимодействия системами фильтрации и митигации провайдера, что снижает количество ложноположительных срабатываний для легитимного трафика клиента. diff --git a/docs/analysis-2026-05-30.md b/docs/[DEPRECATED]-analysis-2026-05-30.md similarity index 100% rename from docs/analysis-2026-05-30.md rename to docs/[DEPRECATED]-analysis-2026-05-30.md diff --git a/docs/auth-architecture.md b/docs/[DEPRECATED]-auth-architecture.md similarity index 100% rename from docs/auth-architecture.md rename to docs/[DEPRECATED]-auth-architecture.md diff --git a/docs/[DEPRECATED]-plan.md b/docs/[DEPRECATED]-plan.md new file mode 100644 index 0000000..f2c24c4 --- /dev/null +++ b/docs/[DEPRECATED]-plan.md @@ -0,0 +1,107 @@ +# IP WhiteList — План разработки + +> **Дата:** 2026-05-30 +> **Основание:** [WhiteIPlist.txt](../WhiteIPlist.txt) (ТЗ) + фактический код в [ipwhitelist-app](../../ipwhitelist-app) +> **Статус:** каноничный — единственный актуальный план + +--- + +## 1. Текущее состояние + +### Готово ✅ + +| Слой | Файлы | Что есть | +|---|---|---| +| БД | `sql/schema.sql` | `companies`, `whitelist_entries` (soft delete), `audit_log` + индексы | +| Валидатор | `src/validators.js` | Все 14 запрещённых диапазонов Приложения А, /22–/32, нормализация host-битов, проверка пересечений | +| CRUD | `src/queries.js` | `createEntry`, `updateEntry`, `deleteEntry` (soft), `listEntries`, `getExportCIDRs`, `getAudit`, `getLimit` | +| UI | `views/index.ejs` | Список, форма добавления, кнопка удаления, стиль Nubes | +| Роуты | `server.js` | `GET /`, `POST /add`, `POST /delete/:id`, `GET /export`, `GET /healthz` | + +### Написано в queries.js, но не подключено к роутам + +- `updateEntry` — нет `POST /update/:id`, нет UI редактирования +- `getAudit` — нет страницы аудита +- `listEntries(companyId, includeDeleted=true)` — флаг есть, не используется + +--- + +## 2. Что требует ТЗ, но отсутствует + +| # | Требование | Готовность | +|---|---|---| +| 1 | Редактирование записи из UI | ❌ queries есть, роута нет | +| 2 | Админ-панель (все компании, лимиты, аудит, фильтры) | ❌ | +| 3 | OIDC Keycloak вместо base64-заглушки | ❌ | +| 4 | Суммаризация CIDR в `/export` | ❌ отдаёт сырой список | +| 5 | Клиентская валидация (JS в форме) | ❌ | +| 6 | Переключатель компаний (multi-company) | ❌ | +| 7 | Просмотр soft-deleted записей админом | ❌ | +| 8 | Изменение `custom_limit` для компании | ❌ | + +--- + +## 3. Порядок реализации + +### Этап 1 — Пользовательский сценарий (client-flow) + +- [x] Валидатор IPv4/CIDR — полный +- [x] Создание / удаление / экспорт +- [ ] **Добавить `POST /update/:id`** в `server.js` +- [ ] **Добавить inline-форму редактирования** в `views/index.ejs` +- [ ] **Клиентская валидация** (JS: формат, маска, длина комментария) + +### Этап 2 — Административная панель + +- [ ] Определение admin-роли (`clientId === 'WZ01112'` + чекбокс) +- [ ] `GET /admin` — страница со всеми компаниями +- [ ] `POST /admin/limit/:companyId` — изменение custom_limit +- [ ] `GET /admin/audit` — журнал аудита +- [ ] Фильтр по компании + показ удалённых записей + +### Этап 3 — Авторизация Keycloak OIDC + +- [ ] `npm install openid-client` +- [ ] Замена base64-decode на проверку подписи JWT +- [ ] Маппинг claims → `req.user` (clientId, email, role) +- [ ] Оставить `DEV_MODE` только для локальной разработки + +### Этап 4 — Экспорт и Multi-company + +- [ ] `npm install cidr-tools` — суммаризация в `GET /export` +- [ ] Переключатель активной компании (если несколько `clientId` в claims) + +### Этап 5 — Завершение + +- [ ] Автотесты (`jest` + `supertest`) +- [ ] Пагинация (если лимит > 50) +- [ ] Сверка всех пунктов ТЗ + +--- + +## 4. Что НЕ делать + +- ❌ Не переписывать на Python/FastAPI +- ❌ Не менять схему БД +- ❌ Не переписывать `validators.js` (он полный) +- ❌ Не переписывать `queries.js` +- ❌ Не делать SPA + +--- + +## 5. Зависимости для установки + +``` +npm install cidr-tools openid-client +npm install --save-dev jest supertest +``` + +Всё остальное — в рамках Express + EJS + pg. + +--- + +## 6. Риски + +- **Admin-роль:** неясно как «отдельный чек-бокс» из ТЗ попадает в токен — требует уточнения с командой Keycloak +- **Multi-company claims:** ТЗ говорит о нескольких компаниях, но в claims только `clientID` — нужен реальный формат +- **IP-ограничение `/export`:** делать в приложении или на уровне ingress — решить при деплое diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..239df86 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,169 @@ +# IP WhiteList — Архитектура (актуально, 2026-05-30) + +> Ветка `sonnet`, репо: `gitea.services.ngcloud.ru/Nail/ipwhitelist-app.git` +> Код: `/home/naeel/ipwhitelist-app` + +--- + +## Стек + +| Слой | Технология | +|---|---| +| Сервер | Node.js + Express 4 | +| Шаблоны | EJS (server-side, без SPA) | +| БД | PostgreSQL, клиент `pg` (pool) | +| Безопасность | helmet, express-rate-limit, csrf-csrf, express-session | +| JWT | jsonwebtoken (mock RS256 / OIDC validation) | +| Авторизация | Keycloak (OIDC) или mock при локальной разработке | + +--- + +## Структура модулей + +``` +server.js — точка входа: init + подключение роутеров (131 строк) +src/ + auth.js — аутентификация: OIDC или mock, session middleware + config.js — MOCK_USERS, backUrl() + db.js — pg pool (checkConnection) + queries.js — все SQL-запросы к БД + validators.js — validateCIDR, aggregateCIDRs + middleware/ + csrf.js — initCsrf() → { doubleCsrfProtection, generateCsrfToken } + rateLimit.js — mutationLimiter (30/мин), exportLimiter (20/мин) + routes/ + auth.js — /login /callback /logout /dev-login + entries.js — / /add /edit/:id /delete/:id + admin.js — /audit /admin /admin/limit/:companyId + export.js — /export +views/ + login.ejs — форма входа (mock) или заглушка (OIDC) + dev-login.ejs — тестовый вход (пресеты + произвольные поля) + index.ejs — список записей пользователя / admin-обзор + admin.ejs — admin-панель (все компании, лимиты) + audit.ejs — лог операций +sql/ + schema.sql — companies, whitelist_entries (soft delete), audit_log +``` + +--- + +## Схема авторизации + +``` +GET /login + ┌─ OIDC-режим (KC_CLIENT_ID задан) ──────────────────────────────────────┐ + │ state → сессия │ + │ redirect → keycloak.nubes.ru/realms/cloud/.../auth │ + │ ↓ KK перенаправляет на /callback │ + │ GET /callback → проверить state → exchangeCode → токен KK │ + │ → userFromPayload → req.session.user → redirect / │ + └─────────────────────────────────────────────────────────────────────────┘ + ┌─ Mock-режим (KC_CLIENT_ID не задан) ───────────────────────────────────┐ + │ render login.ejs (выбрать из MOCK_USERS) │ + │ POST /login → CSRF → req.session.user → redirect / │ + └─────────────────────────────────────────────────────────────────────────┘ + +GET /dev-login (DEV_MODE=true или DEV_SECRET задан) + render dev-login.ejs (пресеты MOCK_USERS + произвольные поля) + POST /dev-login → CSRF → req.session.user → redirect / + ⚠ Работает в ОБОИХ режимах — для тестирования с разными ролями + +GET /logout + session.destroy() + clearCookie('connect.sid') + OIDC: redirect → keycloak logout endpoint + Mock: redirect → /login + +Все защищённые роуты: auth.middleware + req.session.user → req.user (основной путь) + Authorization: Bearer → verify JWT → req.user (API-клиенты) + cookie 'jwt' (legacy) → verify → session → req.user (плавная миграция) + нет ничего → GET: redirect /login, остальное: 401 +``` + +--- + +## JWT claims (auth-api, из реального токена портала) + +> auth-api выпускает свой JWT — **не Keycloak напрямую**. +> Issuer: `"auth-api"`, алгоритм: RS256. +> Наш сервис принимает и валидирует этот токен. + +| Поле | Тип | Пример | Что используем | +|---|---|---|---| +| `ClientID` | string | `"WZ01325"` | `req.user.clientId` | +| `company_id` | UUID | `"3e64aac6-..."` | `req.user.companyId`, FK в БД | +| `company_name` | string | `"Тест"` | `req.user.companyName` | +| `email` | string | `"user@example.com"` | `req.user.email` (аудит) | +| `login` | string | `"user@example.com"` | fallback для email | +| `sub` | UUID | `"0199e325-..."` | fallback для companyId | +| `iss` | string | `"auth-api"` | проверяется при валидации | +| `exp` | number | ~12 часов | автоматически | + +**Признак admin:** `clientId === ADMIN_CLIENT_ID` (env `ADMIN_CLIENT_ID`, default `WZ01112`). +`isAdmin` не приходит в JWT — см. [questions.md](questions.md). + +--- + +## Маршруты + +| Метод | Путь | Доступ | Лимит | +|---|---|---|---| +| GET | `/healthz` | public | — | +| GET | `/.well-known/jwks.json` | public | — | +| GET | `/export` | public | 20/мин | +| GET/POST | `/login` | public | — | +| GET | `/callback` | public | — | +| GET | `/logout` | public | — | +| GET/POST | `/dev-login` | public (с guard) | — | +| GET | `/` | auth | — | +| POST | `/add` `/edit/:id` `/delete/:id` | auth | 30/мин | +| GET | `/audit` `/admin` | auth + admin | — | +| POST | `/admin/limit/:companyId` | auth + admin | 30/мин | + +--- + +## Env-переменные + +### Обязательные в продакшене + +| Переменная | Описание | +|---|---| +| `DB_HOST` / `DB_PORT` / `DB_NAME` / `DB_USER` / `DB_PASS` | PostgreSQL | +| `SESSION_SECRET` | Секрет сессии (≥ 32 символа) | +| `CSRF_SECRET` | Секрет CSRF (≥ 32 символа) | +| `KC_CLIENT_ID` | client_id в Keycloak realm cloud | +| `KC_CLIENT_SECRET` | client_secret | +| `APP_URL` | Внешний URL приложения (для redirect_uri) | + +### Опциональные + +| Переменная | Default | Описание | +|---|---|---| +| `KC_BASE_URL` | `https://keycloak.nubes.ru/realms/cloud` | Keycloak realm base URL | +| `ADMIN_CLIENT_ID` | `WZ01112` | WZ-номер администратора | +| `PORT` | `3000` | HTTP-порт | +| `NODE_ENV` | — | `production` включает secure cookie, trust proxy | +| `DEV_MODE` | `false` | `true` → /dev-login без пароля | +| `DEV_SECRET` | — | Ключ для /dev-login в staging | + +--- + +## БД (dev) + +``` +Host: write.bde8229b-1381-4330-b24b-727ad73fcb44.dev.nubes.ru +DB: ipwhitelist +User: super +``` + +Схема: `sql/schema.sql` — `companies`, `whitelist_entries` (soft delete), `audit_log` + индексы. + +--- + +## Деплой (текущий) + +- URL: `https://white.nodejsk8s.dev.nubes.ru` +- k8s namespace: `whitelist` +- Сессии: in-memory (MemoryStore) — **не масштабируется**, нужен Redis при multi-pod +- Режим: ожидает KC_CLIENT_ID/KC_CLIENT_SECRET для перехода с mock на OIDC diff --git a/docs/plan.md b/docs/plan.md index f2c24c4..cc0280f 100644 --- a/docs/plan.md +++ b/docs/plan.md @@ -1,107 +1,96 @@ -# IP WhiteList — План разработки +# IP WhiteList — План (pending задачи, 2026-05-30) -> **Дата:** 2026-05-30 -> **Основание:** [WhiteIPlist.txt](../WhiteIPlist.txt) (ТЗ) + фактический код в [ipwhitelist-app](../../ipwhitelist-app) -> **Статус:** каноничный — единственный актуальный план +> **Ветка:** `sonnet` +> Всё что было в старых планах — реализовано. Здесь только то, чего ещё нет. --- -## 1. Текущее состояние +## Блокер 1 — Keycloak OIDC (нужны данные от DevOps) -### Готово ✅ +Код OIDC Authorization Code Flow написан и готов (`src/auth.js`). +Не активируется пока не получены: -| Слой | Файлы | Что есть | +| Что нужно | Env-переменная | Статус | |---|---|---| -| БД | `sql/schema.sql` | `companies`, `whitelist_entries` (soft delete), `audit_log` + индексы | -| Валидатор | `src/validators.js` | Все 14 запрещённых диапазонов Приложения А, /22–/32, нормализация host-битов, проверка пересечений | -| CRUD | `src/queries.js` | `createEntry`, `updateEntry`, `deleteEntry` (soft), `listEntries`, `getExportCIDRs`, `getAudit`, `getLimit` | -| UI | `views/index.ejs` | Список, форма добавления, кнопка удаления, стиль Nubes | -| Роуты | `server.js` | `GET /`, `POST /add`, `POST /delete/:id`, `GET /export`, `GET /healthz` | +| client_id в realm cloud | `KC_CLIENT_ID` | ❌ ждём DevOps | +| client_secret | `KC_CLIENT_SECRET` | ❌ ждём DevOps | +| redirect_uri зарегистрирован в KK | (в самом KK) | ❌ ждём DevOps | +| SESSION_SECRET для продакшена | `SESSION_SECRET` | ❌ нужно сгенерировать | -### Написано в queries.js, но не подключено к роутам - -- `updateEntry` — нет `POST /update/:id`, нет UI редактирования -- `getAudit` — нет страницы аудита -- `listEntries(companyId, includeDeleted=true)` — флаг есть, не используется +Без этого сервис работает в mock-режиме (`/dev-login` как тестовый backdoor). --- -## 2. Что требует ТЗ, но отсутствует +## Блокер 2 — Admin-роль из Keycloak -| # | Требование | Готовность | -|---|---|---| -| 1 | Редактирование записи из UI | ❌ queries есть, роута нет | -| 2 | Админ-панель (все компании, лимиты, аудит, фильтры) | ❌ | -| 3 | OIDC Keycloak вместо base64-заглушки | ❌ | -| 4 | Суммаризация CIDR в `/export` | ❌ отдаёт сырой список | -| 5 | Клиентская валидация (JS в форме) | ❌ | -| 6 | Переключатель компаний (multi-company) | ❌ | -| 7 | Просмотр soft-deleted записей админом | ❌ | -| 8 | Изменение `custom_limit` для компании | ❌ | +Сейчас admin = `clientId === WZ01112`. +Как реально приходит `isAdmin` из KK — не ясно (см. [questions.md](questions.md)). +После получения ответа — 1–2 строки в `userFromPayload()` в `src/auth.js`. --- -## 3. Порядок реализации +## Задача 1 — Деплой ветки sonnet -### Этап 1 — Пользовательский сценарий (client-flow) - -- [x] Валидатор IPv4/CIDR — полный -- [x] Создание / удаление / экспорт -- [ ] **Добавить `POST /update/:id`** в `server.js` -- [ ] **Добавить inline-форму редактирования** в `views/index.ejs` -- [ ] **Клиентская валидация** (JS: формат, маска, длина комментария) - -### Этап 2 — Административная панель - -- [ ] Определение admin-роли (`clientId === 'WZ01112'` + чекбокс) -- [ ] `GET /admin` — страница со всеми компаниями -- [ ] `POST /admin/limit/:companyId` — изменение custom_limit -- [ ] `GET /admin/audit` — журнал аудита -- [ ] Фильтр по компании + показ удалённых записей - -### Этап 3 — Авторизация Keycloak OIDC - -- [ ] `npm install openid-client` -- [ ] Замена base64-decode на проверку подписи JWT -- [ ] Маппинг claims → `req.user` (clientId, email, role) -- [ ] Оставить `DEV_MODE` только для локальной разработки - -### Этап 4 — Экспорт и Multi-company - -- [ ] `npm install cidr-tools` — суммаризация в `GET /export` -- [ ] Переключатель активной компании (если несколько `clientId` в claims) - -### Этап 5 — Завершение - -- [ ] Автотесты (`jest` + `supertest`) -- [ ] Пагинация (если лимит > 50) -- [ ] Сверка всех пунктов ТЗ +- [ ] Передеплоить на `white.nodejsk8s.dev.nubes.ru` (сейчас там master) +- [ ] Добавить env-секреты в k8s: `SESSION_SECRET`, `CSRF_SECRET` +- [ ] Проверить smoke-тест: `/healthz`, `/login`, `/export` --- -## 4. Что НЕ делать +## Задача 2 — Сессии при multi-pod -- ❌ Не переписывать на Python/FastAPI -- ❌ Не менять схему БД -- ❌ Не переписывать `validators.js` (он полный) -- ❌ Не переписывать `queries.js` -- ❌ Не делать SPA +Сейчас: MemoryStore (in-process, не масштабируется). +При нескольких репликах k8s сессии будут теряться. + +- [ ] `npm install connect-redis ioredis` +- [ ] Подключить Redis в `server.js` (3 строки конфига) +- [ ] Добавить `REDIS_URL` в env + +**Блокер:** нужен Redis в k8s (или принять ограничение на 1 реплику пока). --- -## 5. Зависимости для установки +## Задача 3 — IP-ограничение /export -``` -npm install cidr-tools openid-client -npm install --save-dev jest supertest -``` +ТЗ: endpoint доступен без авторизации, ограничен по IP на старте. +Текущее решение: открытый endpoint с rate-limit (20 req/мин). -Всё остальное — в рамках Express + EJS + pg. +- [ ] Уточнить: ограничение в приложении или ingress? (см. [questions.md](questions.md)) +- [ ] Если в приложении: whitelist IP через `EXPORT_ALLOWED_IPS` env var --- -## 6. Риски +## Задача 4 — Пагинация -- **Admin-роль:** неясно как «отдельный чек-бокс» из ТЗ попадает в токен — требует уточнения с командой Keycloak -- **Multi-company claims:** ТЗ говорит о нескольких компаниях, но в claims только `clientID` — нужен реальный формат -- **IP-ограничение `/export`:** делать в приложении или на уровне ingress — решить при деплое +При `custom_limit` > 50 таблица станет неудобной. +Сейчас все записи выводятся без пагинации. + +- [ ] `GET /?page=N` + `LIMIT/OFFSET` в `queries.listEntries()` +- [ ] Кнопки Назад/Вперёд в `views/index.ejs` + +Низкий приоритет — текущий дефолтный лимит 15 записей на компанию. + +--- + +## Задача 5 — Auto-dismiss alert + +Flash-алерты (ошибка/успех) сейчас исчезают только при перезагрузке. + +- [ ] 10 строк JS в `views/index.ejs`: `setTimeout(() => alert.remove(), 5000)` + +--- + +## Не делать (закрыто) + +| Что | Почему | +|---|---| +| ~~CSRF-защита~~ | ✅ csrf-csrf (double-submit cookie) | +| ~~Рефакторинг server.js~~ | ✅ 131 строка, роутеры вынесены | +| ~~Автотесты~~ | ✅ 50 unit-тестов (`node tests/run-tests.js`) | +| ~~aggregateCIDRs в /export~~ | ✅ в `src/validators.js` | +| ~~Admin-панель~~ | ✅ `src/routes/admin.js`, `views/admin.ejs` | +| ~~Редактирование записи~~ | ✅ `POST /edit/:id` | +| ~~Аудит~~ | ✅ `GET /audit` | +| ~~OIDC Authorization Code Flow~~ | ✅ `src/auth.js` + `src/routes/auth.js` | +| ~~Session middleware~~ | ✅ `express-session` в `server.js` | +| ~~Dev-login backdoor~~ | ✅ `/dev-login` с пресетами + кастомные поля | diff --git a/docs/questions.md b/docs/questions.md index 8ae9733..1d6ef2d 100644 --- a/docs/questions.md +++ b/docs/questions.md @@ -1,80 +1,70 @@ -# Вопросы для уточнения — IP WhiteList +# Вопросы к DevOps / команде Keycloak — IP WhiteList -> Дата: 2026-05-30 -> Кому: команда Keycloak / Nubes -> Статус: ждём ответов +> Обновлено: 2026-05-30 +> Часть вопросов из первоначального списка закрыта реализацией. --- -## 1. Admin-роль: «отдельный чек-бокс» +## ❌ ОТКРЫТЫЕ (блокируют деплой) -**ТЗ:** Администратор = `clientId = WZ01112` + отдельный чек-бокс. +### 1. Данные клиента Keycloak -**Вопросы:** -- В каком claim приходит этот чек-бокс? (realm_role, client_role, group, attribute?) -- Как точно называется claim? Примеры возможных значений: - - `"roles": ["whitelist-admin"]` - - `"resource_access.whitelist.roles": ["admin"]` - - `"is_admin": true` -- Нужна ли поддержка нескольких админов (не только WZ01112)? +Для активации OIDC-режима (код готов, ждёт переменных): -**Как обойти:** захардкодить `clientId === 'WZ01112'` как признак админа, добавить `TODO` с ссылкой на этот файл. +``` +KC_CLIENT_ID= ? # наш client_id в realm cloud +KC_CLIENT_SECRET= ? # наш client_secret +``` + +Redirect URI для регистрации в Keycloak: +``` +https://white.nodejsk8s.dev.nubes.ru/callback +``` --- -## 2. Multi-company: формат claims +### 2. Admin-роль: как приходит из Keycloak -**ТЗ:** Пользователь может принадлежать нескольким компаниям, в UI — переключатель. +**ТЗ:** Администратор = `clientId = WZ01112` + «отдельный чек-бокс». -**Вопросы:** -- `clientID` в токене — это строка или массив строк? -- Если массив — как называется claim? (`clientIDs`, `groups`, что-то ещё?) -- Есть ли claim с названием компании (для отображения в переключателе)? -- Пример реального payload токена (без секретов) для пользователя с 2+ компаниями. +Сейчас: `isAdmin = (ClientID === process.env.ADMIN_CLIENT_ID)` — только WZ01112. -**Как обойти:** всегда считать `clientId` строкой (один клиент), переключатель не делать, добавить `TODO`. +Вопросы: +- Есть ли отдельный claim в JWT для признака admin? +- Если да — как называется? Примеры: `realm_access.roles`, `resource_access.whitelist.roles`, `is_admin`, `groups`… +- Нужна поддержка нескольких adminов или только WZ01112? + +**Если нет отдельного claim** — оставляем текущее решение, закрываем вопрос. --- -## 3. IP-ограничение /export +### 3. /export — IP-ограничение -**ТЗ:** Endpoint экспорта «на старте может работать без авторизации (по сетевому ограничению)». +ТЗ: «на старте может работать без авторизации (по сетевому ограничению)». -**Вопросы:** -- Ограничение делаем в приложении или на уровне ingress (nginx/traefik)? -- Если в приложении — где взять список разрешённых IP? (env var, файл, БД?) -- Если ingress — кто настраивает? +Сейчас: открытый endpoint, rate-limit 20 req/мин. -**Как обойти:** оставить `/export` открытым, добавить `TODO`. +- Ограничение делаем в **приложении** или в **ingress/nginx**? +- Если в приложении — список разрешённых IP (env var `EXPORT_ALLOWED_IPS`?). --- -## 4. Email пользователя +## ✅ ЗАКРЫТЫЕ -**ТЗ:** `email` — идентификация пользователя для аудита. - -**Вопросы:** -- Как называется claim с email? (`email`, `preferred_username`, что-то ещё?) -- Всегда ли он присутствует в токене? - -**Как обойти:** брать `payload.email` с fallback на `payload.sub`, добавить `TODO`. - ---- - -## 5. DEV_MODE - -**Вопросы:** -- Нужен ли dev-режим на платформе Nubes (не локально)? -- Или всегда только реальный Keycloak? - -**Как обойти:** оставить `DEV_MODE=true` с проверкой что в production падает при включении. +| # | Вопрос | Решение | +|---|---|---| +| DEV_MODE | Нужен ли dev-режим на платформе? | `/dev-login` — backdoor с `DEV_MODE=true` (без пароля) или `DEV_SECRET=xyz` (с ключом). Работает в обоих режимах. | +| Email claim | Как называется claim с email? | Берём `payload.email \|\| payload.login \|\| 'unknown'` | +| JWT структура | Формат токена auth-api | Изучен из реального токена (см. [DEPRECATED]-auth-architecture.md). Поля: `ClientID`, `company_id`, `company_name`, `email`, `login`. | +| CSRF | Защита POST-форм | csrf-csrf (double-submit cookie pattern) | +| Session | Хранение пользователя | express-session (httpOnly, sameSite lax, 8ч) | --- ## Условные обозначения в коде +```js +// TODO(KK): уточнить формат claim — см. docs/questions.md#2 +// FIXME(KK): временно, заменить после ответа команды ``` -// TODO(Keycloak): уточнить формат claim — см. docs/questions.md#1 -// FIXME(Keycloak): временно, заменить после ответа команды -// HACK(DEV_MODE): заглушка, убрать перед продакшеном -``` +