docs: актуализация (2026-05-30)

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
This commit is contained in:
“Naeel”
2026-05-30 14:42:52 +03:00
parent 9ad7eb9a8d
commit 608560eb64
7 changed files with 420 additions and 129 deletions
+67 -78
View File
@@ -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` с пресетами + кастомные поля |