diff --git a/docs/auth-architecture.md b/docs/auth-architecture.md new file mode 100644 index 0000000..e36c880 --- /dev/null +++ b/docs/auth-architecture.md @@ -0,0 +1,83 @@ +# Архитектура авторизации облачного портала + +> Со слов пользователя, на основе JWT из localStorage + +--- + +## Схема аутентификации + +``` +Пользователь (браузер) + │ + ▼ +Keycloak (keycloak.nubes.ru, realm=cloud) + │ Authorization Code Flow + │ client_id=deck.ngcloud.ru + │ scope=openid email + ▼ +auth-api.ngcloud.ru (СОБСТВЕННЫЙ сервис) + │ Создаёт свой JWT (issuer="auth-api") + │ Подпись: RS256 (RSA) + ▼ +deck.ngcloud.ru (портал) + │ Микро-фронтенды (SPA): dashboard, contracts, services, ... + │ JWT хранится в localStorage: authApiTokens.access_token + ▼ +IPWhiteList (наш сервис) ← будет встроен как микро-фронтенд в портал +``` + +## Важное + +- JWT подписывает **не Keycloak**, а **auth-api** +- Issuer: `"auth-api"`, алгоритм: `RS256` +- Для валидации нужен публичный ключ auth-api (JWKS или статический) + +--- + +## Структура JWT (access_token) + +_Из localStorage → authApiTokens → access_token_ + +| Поле | Тип | Значение (пример) | Назначение | +|---|---|---|---| +| `iss` | string | `"auth-api"` | Кто выпустил токен | +| `sub` | string | `"0199e325-..."` | UUID пользователя | +| `iat` | number | `1780073927` | Выпущен (Unix time) | +| `exp` | number | `1780117127` | Истекает (~12 часов) | +| `jti` | string | `"4d8d7240-..."` | Уникальный ID токена | +| `ClientID` | string | `"WZ01325"` | ID **ТЕКУЩЕЙ** компании пользователя | +| `company_id` | string (UUID) | `"3e64aac6-..."` | UUID компании | +| `company_name` | string | `"Тест"` | Название компании | +| `email` | string | `"tazet@narod.ru"` | Email (для аудита) | +| `login` | string | `"tazet@narod.ru"` | Логин | +| `firstname` | string | `"Наиль"` | Имя | +| `lastname` | string | `"Тазетдинов"` | Фамилия | +| `token_type` | string | `"access"` | Тип токена | + +## Что НЕ в JWT (отдельный authData в localStorage) + +_Из localStorage → authData → v_ + +| Поле | Значение | Где используется | +|---|---|---| +| `userInfo.isAdmin` | `false` (boolean) | ⚠️ Признак администратора | +| `profiles[]` | Массив `{company_id, company_name, is_active_profile}` | Список всех компаний пользователя | +| `roles[]` | Массив `{role_id, role_name}` | Роли пользователя | +| `permissions.can_write` | `false` | Есть ли права на запись | + +--- + +## Открытые вопросы (нужно уточнить с командой портала) + +1. **Валидация JWT.** У auth-api есть JWKS (`/.well-known/jwks.json`)? Или ключ статический? Как нашему сервису проверять подпись? + +2. **isAdmin.** Флаг админа есть только в authData localStorage, но не в JWT. Как наш сервис узнает что пользователь — админ? + - Вариант A: попросить добавить `isAdmin` в JWT + - Вариант B: наш сервис ходит в auth-api за authData + - Вариант C: смотреть на `ClientID == "WZ01112"` (как в ТЗ) + +3. **Список компаний.** В JWT — только одна компания. В authData.profiles — массив. Для переключателя компаний в нашем UI — откуда брать список? + - Вариант A: попросить добавить `profiles` в JWT + - Вариант B: наш сервис ходит в auth-api + +4. **Как наш сервис попадает в портал?** Как микро-фронтенд (SPA) или как отдельная страница с редиректом? Куда монтироваться?