diff --git a/.gitignore b/.gitignore index 1215ae2..428fc37 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,8 @@ __pycache__/ .idea/ Files/nubes_login.har +token*.* + # Внутренняя аналитика — не для публичного доступа PROJECT-AUDIT.md AGENT-DIAGNOSIS.md diff --git a/docs/keycloak-auth-reference.md b/docs/keycloak-auth-reference.md new file mode 100644 index 0000000..02bf3d4 --- /dev/null +++ b/docs/keycloak-auth-reference.md @@ -0,0 +1,205 @@ +# Keycloak Auth Reference — консолидированная справка + +> Обновлено: 2026-06-02 +> Файл-ориентир. Править по мере уточнения у DevOps и продвижения проекта. + +--- + +## 1. Keycloak-эндпоинты + +Realm: **`cloud`** на `keycloak.nubes.ru` + +| Назначение | URL | +|---|---| +| Authorization | `https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/auth` | +| Token exchange | `https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/token` | +| JWKS (сертификаты) | `https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/certs` | +| Logout | `https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/logout` | + +--- + +## 2. Зарегистрированное приложение (OAuth client) + +| Параметр | Значение | +|---|---| +| `client_id` | `deck-test.ngcloud.ru` | + +Источник: HAR-файл `Files/nubes_login.har` (в query-параметрах auth request). + +--- + +## 3. Схема аутентификации: Authorization Code Flow + +``` +1. Браузер → /login (приложение) +2. Приложение → 302 → Keycloak /auth?client_id=...&scope=openid&redirect_uri=...&state=... +3. Пользователь вводит логин/пароль в Keycloak +4. Пользователь вводит OTP (MFA) +5. Keycloak → 302 → /callback?code=...&state=... +6. Приложение → POST /token (code → access_token + refresh_token + id_token) +7. Валидация JWT через JWKS (RS256) +8. Извлечение claims → req.user → сессия +``` + +--- + +## 4. Получение токена через API Gateway (альтернативный путь) + +Из HAR: код обменивается не напрямую в Keycloak, а через **API Gateway ngcloud**: + +``` +POST https://lk-api-gateway-test.ngcloud.ru/api/v1/iam/auth/token +Content-Type: application/json +Body: {"code": ""} +``` + +Ответ: +```json +{ + "access_token": "...", + "refresh_token": "...", + "expires_in": 43200 +} +``` + +--- + +## 5. JWT-токены + +### 5.1. Токен из `Files/token.txt` (test-окружение) + +| Поле | Значение | +|---|---| +| `iss` | `https://auth.k8s.ngcloud.ru` | +| `aud` | `backend` | +| `sub` | `tazetdinovn@gmail.com` | +| `name` | Наиль Тазетдинов | +| `email` | `tazetdinovn@gmail.com` | +| `expires_in` | 86400 (24ч) | +| `scope` | `openid` | +| `token_type` | `Bearer` | + +Claims (массив): +- `""` +- `""` +- `wz03709-shturval-admin-iot-naeel` — namespace в k8s (Fission) +- `wz03709-shturval-admin-naeel-test-3` — ещё один namespace + +> ⚠️ `ClientID` в этом токене **отсутствует** — это другой issuer (`auth.k8s.ngcloud.ru`), не Keycloak. + +### 5.2. Токен из HAR (deck-test) + +| Поле | Значение | +|---|---| +| `iss` | `auth-api` | +| `ClientID` | `WZ01325` | +| `company_id` | `3e64aac6-dcfc-4082-88dc-da19c86555a5` | +| `company_name` | Тест | +| `login` | `tazet@narod.ru` | +| `firstname` | Наиль | +| `lastname` | Тазетдинов | +| `email` | `tazet@narod.ru` | + +### 5.3. Целевой формат токена (из ТЗ) + +| Claim | Назначение | +|---|---| +| `ClientID` | Идентификатор компании (WZ-номер) | +| `company_id` | UUID компании | +| `company_name` | Название компании | +| `email` | Email пользователя (для аудита) | +| `groups` | ? (пока null) | +| `roles` | ? (пока null) | + +--- + +## 6. Роли + +| Роль | ClientID | Права | +|---|---|---| +| **Администратор** | `WZ01112` | Все записи, просмотр аудита, экспорт | +| **Клиент** | любой другой WZ-номер | Только свои записи | + +> Как именно ClientID попадает в токен — через группы Keycloak или маппер — **требует уточнения у DevOps**. + +--- + +## 7. Реализация в коде + +### `src/auth.js` — два режима + +| Режим | Условие | Как работает | +|---|---|---| +| **OIDC** | `KC_CLIENT_ID` + `KC_CLIENT_SECRET` заданы | Authorization Code Flow, JWKS-валидация | +| **Mock** | `KC_CLIENT_ID` пусто | Локальная RSA-пара, JWT из `MOCK_USERS` | + +### `src/config.js` — Mock-пользователи + +```javascript +MOCK_USERS = [ + { clientId: 'WZ01325', companyName: 'Тест', email: 'tazet@narod.ru' }, + { clientId: 'WZ01112', companyName: 'Нубес', email: 'admin@nubes.ru' } +] +``` + +### Два слоя аутентификации + +| Слой | Механизм | Где | +|---|---|---| +| **UI (EJS)** | Session cookie + PostgreSQL session store | `/`, `/admin`, `/entries` | +| **REST API** | Bearer JWT (всегда проверяется явно) | `/api/v1/*` | + +### Dev-backdoor + +- `DEV_MODE=true` или `DEV_SECRET` → `/dev-login` — выбор любого пользователя в обход OIDC/mock + +--- + +## 8. Переменные окружения + +```env +# Auth +DEV_MODE=true +JWT_ISSUER=mock-auth-api +ADMIN_CLIENT_ID=WZ01112 + +# OIDC (production) +KC_BASE_URL=https://keycloak.nubes.ru/realms/cloud +KC_CLIENT_ID= +KC_CLIENT_SECRET= +APP_URL=http://localhost:3000 + +# Session +SESSION_SECRET=<обязателен в prod> +``` + +--- + +## 9. Открытые вопросы (к DevOps) + +| # | Вопрос | Статус | +|---|---|---| +| 1 | Где брать `client_id` и `client_secret` для регистрации приложения в Keycloak? | 🔴 | +| 2 | Какой URL JWKS? `auth-api` или `keycloak.nubes.ru`? | 🔴 | +| 3 | Когда заполняются `claims.groups` и `claims.roles`? (пока null) | 🔴 | +| 4 | Как выглядит токен, если у пользователя несколько компаний? Массив `ClientID`? | 🔴 | +| 5 | Есть ли в Keycloak понятие admin-роли? В каком claim? | 🔴 | +| 6 | Redirect URI: `https://white.nodejsk8s.dev.nubes.ru/callback` — OK? | 🔴 | +| 7 | Кто потребитель `/export`? Какие IP/подсети для allowlist? | 🔴 | +| 8 | Формат `jsonEnv` — пример правильного? | 🔴 | + +--- + +## 10. Исходные файлы + +| Файл | Содержание | +|---|---| +| `docs/ТЗ.md` | Техническое задание | +| `docs/ТЗ-реализация.md` | Карта реализации требований | +| `docs/questions-devops.md` | Вопросы к DevOps | +| `research/auth-flow.md` | Детальное исследование OIDC-flow | +| `research/payg-sso/` | CF-эталон (OAuth2 + JWT RS256) | +| `Files/token.txt` | Реальный токен из test-окружения | +| `Files/nubes_login.har` | HAR трассировка login-сессии deck-test | +| `src/auth.js` | Основной модуль аутентификации | +| `src/config.js` | Конфигурация и mock-пользователи | diff --git a/docs/tests-multi-company.md b/docs/tests-multi-company.md new file mode 100644 index 0000000..83086d2 --- /dev/null +++ b/docs/tests-multi-company.md @@ -0,0 +1,104 @@ +# Тесты: мульти-компания (2026-06-02) + +> Запускать против `https://white.nodejsk8s.dev.nubes.ru` +> Куки сохраняются между запросами (использовать `-c jar.txt -b jar.txt`) + +## 1. Логин под мульти-компанией + +```bash +# Получить CSRF-токен и логин-форму +curl -s -c jar.txt https://white.nodejsk8s.dev.nubes.ru/login > /dev/null + +# Извлечь CSRF-токен +CSRF=$(curl -s -b jar.txt https://white.nodejsk8s.dev.nubes.ru/login | grep -oP 'name="_csrf" value="\K[^"]+') + +# Залогиниться как "multi" +curl -s -b jar.txt -c jar.txt \ + -X POST https://white.nodejsk8s.dev.nubes.ru/login \ + -d "user=multi&_csrf=$CSRF" \ + -D - | head -1 +# Ожидаем: HTTP/1.1 302 → / + +# Проверить что сессия содержит allClientIds +curl -s -b jar.txt https://white.nodejsk8s.dev.nubes.ru/ | grep -c 'WZ01325' +# Ожидаем: >0 (активная компания в интерфейсе) +``` + +## 2. Переключатель компаний в UI + +```bash +# Страница должна содержать переключатель ( + <% allClientIds.forEach(id => { %> + + <% }) %> + + + + Компаний: <%= allClientIds.length %> + + + + + <% } %> +