Files

210 lines
8.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SSO / Auth — исследование и контекст
> Дата: 2026-05-30
> Актуально для ветки `sonnet`
---
## Исходные данные от команды
### Ссылки (получены 2026-05-30)
| Что | URL | Примечание |
|---|---|---|
| Пример SSO на ColdFusion | https://gitea.services.ngcloud.ru/smishchuk/payg-report.git | Написан «на коленке», но рабочий |
| Конфигурация сервиса в Deck | https://deck.ngcloud.ru/services/instance/detail/523413d2-1ffd-457b-b0e3-0a8185fe9358 | Там видно как кладутся env-переменные |
### Файлы из payg-report сохранены локально
```
research/payg-sso/
Application.cfc — основной SSO flow (редирект, обмен code→token, refresh, logout)
lib_oauth2.cfc — Authorization Code Flow клиент
lib_jwt.cfc — валидация JWT RS256
```
---
## Keycloak — эндпоинты
Realm: `cloud`
Base: `https://keycloak.nubes.ru/realms/cloud`
| Назначение | URL |
|---|---|
| Авторизация (браузер → KK) | `https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/auth` |
| Обмен code → token | `https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/token` |
| Logout | `https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/logout` |
| JWKS (публичные ключи) | `https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/certs` |
| OpenID конфигурация | `https://keycloak.nubes.ru/realms/cloud/.well-known/openid-configuration` |
---
## Реальная схема аутентификации
```
Браузер
│ 1. GET / (без токена)
наше приложение
│ 2. redirect → KK /auth?client_id=...&redirect_uri=...&scope=openid
Keycloak (keycloak.nubes.ru, realm=cloud)
│ 3. Пользователь логинится
│ 4. KK redirect → наш /callback?code=...
наше приложение
│ 5. POST /token (code + client_secret) → access_token + refresh_token
│ 6. Валидация JWT через JWKS (certs endpoint)
│ 7. Извлечение claims из JWT → req.user
Работаем: req.user.ClientID = "WZ01325" и тд
```
**Важно из примера payg-report:**
- Тип flow: Authorization Code Flow (не Implicit, не PKCE — обычный)
- `client_secret` передаётся при обмене code→token (confidential client)
- Сертификат берётся с JWKS endpoint при старте, кэшируется в application scope
- При истечении части времени жизни — автоматический refresh через refresh_token
---
## JWT — структура токена
JWT выпускает **auth-api** (не Keycloak напрямую).
Алгоритм: **RS256**.
JWKS для валидации: из auth-api (уточнить URL у DevOps).
> ⚠️ В payg-report используют `keycloak.nubes.ru/certs`, но наш портал deck.ngcloud.ru
> идёт через auth-api.ngcloud.ru — нужно уточнить чей именно JWT будет у нас.
### Claims в JWT (из реального токена, secrets.txt)
| Claim | Тип | Пример | Назначение |
|---|---|---|---|
| `iss` | string | `"auth-api"` | Издатель |
| `sub` | string | `"0199e325-1cdf-..."` | UUID пользователя |
| `exp` | number | `1795627443` | Срок действия |
| `iat` | number | `1780075443` | Выпущен |
| `ClientID` | string | `"WZ01325"` | **WZ-номер компании** ← ключевой claim |
| `company_id` | string (UUID) | `"3e64aac6-dcfc-..."` | UUID компании |
| `company_name` | string | `"Тест"` | Название компании |
| `email` | string | `"tazet@narod.ru"` | Email (для аудита) |
| `login` | string | `"tazet@narod.ru"` | Логин |
| `firstname` | string | `"Наиль"` | Имя |
| `lastname` | string | `"Тазетдинов"` | Фамилия |
| `token_type` | string | `"tech"` | Тип токена |
**Пример декодированного payload** (из secrets.txt — не секрет, payload JWT не зашифрован):
```json
{
"iss": "auth-api",
"sub": "0199e325-1cdf-7cda-9319-e5302a85e291",
"ClientID": "WZ01325",
"company_id": "3e64aac6-dcfc-4082-88dc-da19c86555a5",
"company_name": "Тест",
"email": "tazet@narod.ru",
"login": "tazet@narod.ru",
"token_type": "tech"
}
```
---
## Конфигурация сервиса (env-переменные)
Из примера payg-report и Deck — конфиги кладутся в переменные окружения:
```bash
# Нужно получить от DevOps:
KC_CLIENT_ID=white.nodejsk8s.dev.nubes.ru # имя нашего клиента в KK (уточнить)
KC_CLIENT_SECRET=<секрет> # из Deck / у DevOps
JWKS_URL=https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/certs
# ИЛИ если через auth-api:
# JWKS_URL=https://auth-api.ngcloud.ru/.well-known/jwks.json
# В payg-report переменная называется:
IDP_CLIENT_SECRET=<секрет>
```
---
## Что сейчас в нашем коде
Файл: `src/auth.js`
| Режим | Условие | Поведение |
|---|---|---|
| **DEV_MODE** | `DEV_MODE=true` и не production | Авторизация пропущена, юзер захардкожен из `MOCK_USERS` |
| **JWKS_URL задан** | `JWKS_URL != ''` | Валидирует Bearer-токен из заголовка через JWKS |
| **Мок RS256** | Ничего из выше | Генерирует собственную RSA-пару, выдаёт и валидирует JWT сам |
**Чего не хватает для продакшена:**
- Authorization Code Flow (редирект на KK и обратно)
- Обмен `code``access_token` через `/token`
- Session/cookie для хранения токена между запросами
- Refresh токена
---
## Пример SSO flow из payg-report (ключевые строки)
```javascript
// Настройка (из окружения):
const client_id = process.env.IDP_CLIENT_SECRET; // их название
const auth_endpoint = 'https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/auth';
const token_endpoint = 'https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/token';
const certs_url = 'https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/certs';
// 1. Нет сессии → redirect на KK:
location(idp.buildRedirectToAuthURL({scope: 'openid profile email', state: guid}));
// 2. KK вернул ?code=... → меняем на токен:
const resp = idp.makeAccessTokenRequest(url.code);
// 3. Парсим JWT из ответа:
const token_data = jwt.decode(resp.access_token, idpCertificate, 'RS256');
session.auth.wz = token_data.ClientID; // WZ-номер компании
session.auth.login = token_data.preferred_username;
// 4. Refresh когда прошло > 2/3 времени жизни (только на GET):
if (timeSinceIat > refresh_expires_in / 1.5 && method === 'GET') {
const resp = idp.refreshAccessTokenRequest(session.auth.refresh_token);
// ... обновляем сессию
}
```
---
## Открытые вопросы (нужен ответ от DevOps/KK-команды)
| # | Вопрос | Влияет на |
|---|---|---|
| 1 | Какой `client_id` выдан нашему сервису в KK realm `cloud`? | Конфиг |
| 2 | Где взять `client_secret`? | Конфиг |
| 3 | Токен от KK или от auth-api? Соответственно — какой JWKS_URL? | `src/auth.js` |
| 4 | Claim для admin-роли: чек-бокс — это `realm_roles`, `resource_access`, attribute? | `src/auth.js` requireAdmin |
| 5 | `ClientID` в токене — строка или массив (multi-company)? | `src/queries.js`, UI |
| 6 | `/export` — нужна ли авторизация или только сетевое ограничение по IP? | `src/routes/export.js` |
---
## API Gateway (для справки)
Из `docs/auth-architecture.md`:
- Gateway: `lk-api-gateway.ngcloud.ru`
- JWT из localStorage портала: `authApiTokens.access_token`
- **DDOS-Guard cookies обязательны** при curl-тестировании: `__ddg1_`, `__ddg8_`, `__ddg9_`, `__ddg10_`
```bash
# Пример рабочего curl (2026-05-29):
curl -H "Authorization: Bearer <JWT>" \
-H "Origin: https://deck.ngcloud.ru" \
-H "Cookie: __ddg1_=...; __ddg8_=...; __ddg9_=...; __ddg10_=..." \
"https://lk-api-gateway.ngcloud.ru/api/v1/..."
```