# IP WhiteList — Архитектура (актуально, 2026-05-30 14:43) > Ветка `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