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:
@@ -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
|
||||
Reference in New Issue
Block a user