# Состояние проекта на 2026-05-30 14:43 (ветка `sonnet`) ## Репозитории | Репо | URL | Ветка | Локальный путь | |---|---|---|---| | Код приложения | `https://gitea.services.ngcloud.ru/Nail/ipwhitelist-app.git` | **sonnet** | `/home/naeel/ipwhitelist-app` | | Документация | `https://gitea.services.ngcloud.ru/Nail/IPWhiteList.git` | main | `/home/naeel/IPWhiteList` | ## Стек Node.js + Express + EJS + PostgreSQL + pg pool + jsonwebtoken ## Деплой - URL: `https://white.nodejsk8s.dev.nubes.ru` - DEV_MODE=true (мок-аутентификация) - ⚠️ Нужен передеплой Nubes для ветки sonnet ## БД - `write.bde8229b-1381-4330-b24b-727ad73fcb44.dev.nubes.ru` - user: `super`, db: `ipwhitelist` - Миграция от 2026-05-30 применена (CHECK, UNIQUE, индексы) --- ## Что сделано в ветке `sonnet` (4 коммита поверх master) ### 1. `src/validators.js` — CIDR агрегация - `aggregateCIDRs(cidrs)` — суммаризация: merge пересекающихся + смежных диапазонов → минимальный набор CIDR - Вспомогательные: `numToIP(n)`, `rangeToCIDRs(start, end)` - Экспортируется и используется в `/export` ### 2. `src/auth.js` — admin-роль - `ADMIN_CLIENT_ID` = `process.env.ADMIN_CLIENT_ID || 'WZ01112'` - `req.user.isAdmin` — определяется по `clientId === ADMIN_CLIENT_ID` - `DEV_ADMIN=true` в `.env` → admin-права в dev-режиме - `requireAdmin` middleware — 403 для не-admin ### 3. `src/queries.js` — новые функции - `getCompanyById(id)` — компания по числовому PK - `getAllCompanies()` — все компании + `active_count` (LEFT JOIN) - `setLimit(companyId, newLimit)` — установить/сбросить (null) индивидуальный лимит - `getAudit()` — обновлён: JOIN с companies (поля `company_name`, `client_id`) ### 4. `server.js` — новые роуты - `/export` — публичный (до auth.middleware), возвращает агрегированный список всех компаний - `GET /` — admin: видит все компании с переключателем `?company=X` - `POST /edit/:id` — редактирование записи (admin + user) - `GET /audit` — журнал аудита (только admin) - `GET /admin` — управление лимитами (только admin) - `POST /admin/limit/:companyId` — изменить/сбросить лимит компании - `backUrl()` — хелпер для редиректа обратно с учётом контекста admin/user ### 5. `views/index.ejs` - Admin-панель выбора компании (dropdown + быстрые ссылки на Аудит/Лимиты) - Кнопки Аудит/Лимиты/Выйти в header для admin - Кнопка "Изменить" в таблице (открывает edit modal) - Edit modal — overlay с формой, закрывается по Escape/backdrop - Скрытый `company_id` в формах add/delete для корректной admin-ветки ### 6. `views/audit.ejs` — **новый** - Таблица журнала аудита с фильтром по компании - Цветные badges (CREATE/UPDATE/DELETE) - old_value → new_value стрелочкой ### 7. `views/admin.ejs` — **новый** - Таблица всех компаний: client_id, active_count, лимит - Прогресс-бар использования (зелёный/янтарный/красный) - Форма изменения лимита с подтверждением; кнопка ↺ сброс на дефолт --- ## Не сделано (production-hardening, не баги) - helmet (X-Frame-Options, CSP, HSTS) - rate-limit на POST /add, /delete, /export - CSRF-токены в формах - JWT-верификация через внешний JWKS (нужен URL от девопсов) - Multi-company (нужен формат claims от платформы — массив clientId?) ## Мёртвый код (не критично) - `isSubnetOf()` в validators.js — определена, не используется, не экспортируется ## Для прода Выставить в `.env`: ``` NODE_ENV=production JWKS_URL= ADMIN_CLIENT_ID= DEFAULT_LIMIT=15 ``` ## Репозитории | Репо | URL | Ветка | Локальный путь | |---|---|---|---| | Код приложения | `https://gitea.services.ngcloud.ru/Nail/ipwhitelist-app.git` | master | `/home/naeel/ipwhitelist-app` | | Документация | `https://gitea.services.ngcloud.ru/Nail/IPWhiteList.git` | main | `/home/naeel/IPWhiteList` | ## Стек Node.js + Express + EJS + PostgreSQL + pg pool + jsonwebtoken ## Деплой - URL: `https://white.nodejsk8s.dev.nubes.ru` - DEV_MODE=true (мок-аутентификация) - ⚠️ Код запушен, но Nubes не передеплоил — крутится старая версия ## БД - `write.bde8229b-1381-4330-b24b-727ad73fcb44.dev.nubes.ru` - user: `super`, db: `ipwhitelist` - Миграция от 2026-05-30 применена (CHECK, UNIQUE, индексы) --- ## Что сделано (запушено) ### 1. `src/validators.js` — исправлены 3 бага - Запрещённые диапазоны: `isSubnetOf` → `overlaps` (обход через суперсеть `/22`) - Маска: `parseInt('24abc')` глотал мусор → строгая проверка `/^\d{1,2}$/` - Множественные слэши: `10.0.0.0/24/8` теперь отклоняется ### 2. `src/queries.js` — транзакции + гонки - `createEntry`, `updateEntry`, `deleteEntry` — внутри транзакции с `SELECT ... FOR UPDATE` - `getOrCreateCompany` — атомарный `INSERT ... ON CONFLICT` - `getLimit` — `!= null` вместо `||` (custom_limit=0 не игнорируется) - `logAudit` — принимает клиента транзакции (пишется атомарно) - `getExportCIDRs` — фильтр по `companyId` - `deleteEntry` — `company_id` в WHERE ### 3. `src/auth.js` — **новый.** Мок JWT-аутентификация - Генерирует RSA-ключи при старте - JWKS endpoint: `/.well-known/jwks.json` - `verifyJWT(token)` — RS256, issuer: `mock-auth-api` - `issueJWT(claims)` — выпускает токен с claims как в HAR (`ClientID`, `company_id`, `company_name`, `email`) - Middleware: извлекает JWT из cookie (`jwt`) или `Authorization: Bearer` - DEV_MODE: при `DEV_MODE=true && NODE_ENV!=production` — обход auth - **Для прода:** выставить `JWKS_URL=https://auth-api.../jwks` → switches to external verification ### 4. `views/login.ejs` — **новый.** Мок-страница входа - Выбор из 3 пользователей (admin WZ01112, тест WZ01325, компания 2 WZ02001) - В проде заменяется на редирект в Keycloak ### 5. `server.js` - `cookie-parser` для чтения JWT из cookie - `/healthz` — выше auth (k8s probe) - `/login` GET/POST — мок-логин - `/logout` — чистит cookie - `/export` — только для своей компании (с авторизацией) - `req.query.error` читается - `urlencoded({ limit: '32kb' })` ### 6. `sql/schema.sql` - UNIQUE INDEX на активный `(company_id, value_cidr)` WHERE deleted_at IS NULL - CHECK на `value_cidr` формат - CHECK на `audit_log.action IN ('CREATE','UPDATE','DELETE')` - CHECK на `custom_limit IS NULL OR >= 0` - FK: `ON DELETE RESTRICT` - Составной индекс `(company_id, created_at DESC)` на audit_log - Индекс `(company_id, created_at DESC)` на whitelist_entries ### 7. `views/index.ejs` - `pattern` + `maxlength="18"` + `title` на инпуте value --- ## Ревью (`/home/naeel/IPWhiteList/research/`) | Файл | Что | |---|---| | `REVIEW-SUMMARY.md` | Сводка всех находок (11 критических, 21 средний) | | `opus-review-validators.md` | 1 критичный + 2 средних | | `opus-review-queries.md` | 3 гонки + audit + getLimit | | `opus-review-server.md` | JWT без подписи, 401, CSRF, /export | | `opus-review-schema.md` | UNIQUE, CIDR, FK, индексы | | `opus-review-ejs.md` | CSRF, clickjacking, client-валидация | | `auth-flow.md` | Анализ HAR: claims, цепочка auth-api | --- ## Не сделано (production-hardening, не баги) - helmet (X-Frame-Options, CSP, HSTS) - rate-limit на POST /add, /delete, /export - CSRF-токены в формах - JWT-верификация через внешний JWKS (нужен URL от девопсов) - Admin-признак в токене (нужен пример токена админа) - Multi-company (нужен формат claims от платформы) ## Для прода Выставить в `.env`: ``` NODE_ENV=production JWKS_URL= ``` Всё остальное работает без изменений.