# Интеграция auth-portal ↔ CDN Manager Единый вход: пользователь логинится на auth-portal, получает JWT, переходит в CDNManager с токеном в URL fragment. CDNManager API проверяет JWT и права `cdn:*`. ## Архитектура ``` Browser → CDNManager UI (нет token) → redirect AUTH_PORTAL_URL/?return_to=…/auth/callback → login → redirect return_to#access_token=… → CDNManager /auth/callback сохраняет token (cdnmanager_token) → API Authorization: Bearer … ``` Общий секрет: `JWT_SECRET` / `AUTH_JWT_SECRET` (HS256). Issuer: `ISSUER` / `AUTH_ISSUER`. App id в портале: **`cdn`** (каталог permissions). ## Локальный запуск ### 1. auth-portal ```bash cd auth-portal pnpm install # JWT_SECRET=dev-secret-change-me # RETURN_TO_ALLOWLIST=.shnt.top,localhost,http://localhost:5173,…,http://localhost:5176 pnpm --filter @authportal/api dev # :8080 pnpm --filter web dev # :5175 ``` В `apps/web/.env.local` (опционально для App Switcher / SSO open): ```env VITE_CDN_APP_URL=http://localhost:5176 ``` Bootstrap: `admin@shnt.top` / `admin`. В админке выдайте app **cdn** и permissions `cdn:*`. ### 2. CDNManager ```bash cd CDNManager pnpm install ``` Корень / API: ```env AUTH_REQUIRED=true AUTH_JWT_SECRET=dev-secret-change-me AUTH_ISSUER=https://auth.shnt.top AUTH_PORTAL_URL=http://localhost:5175 CLOUDFLARE_API_TOKEN= ``` `apps/web/.env.local`: ```env VITE_AUTH_ENABLED=true VITE_AUTH_PORTAL_URL=http://localhost:5175 ``` Порт Vite web — `5176` (`apps/web/vite.config.ts`). Добавьте origin в `RETURN_TO_ALLOWLIST` портала. ```bash pnpm --filter @cdnmanager/api dev pnpm --filter web dev ``` Откройте CDNManager → редирект на portal → после логина NavUser показывает имя/email. ## Permissions ↔ API / UI Иерархия: `admin` ⊃ `write` ⊃ `read` в рамках одной секции. | Permission | API | UI | |------------|-----|-----| | `cdn:dashboard:read` | GET `/api/v1/dashboard/*`, `/topology` | `/` | | `cdn:nodes:read` | GET `/api/v1/nodes*`, `/locations` | `/nodes` | | `cdn:nodes:write` | POST/PATCH/DELETE nodes | create/edit нод | | `cdn:aliases:read` | GET `/api/v1/aliases*` | `/aliases` | | `cdn:aliases:write` | POST/PATCH aliases, retarget | create / Retarget | | `cdn:zones:read` | GET `/api/v1/zones*` | `/zones` | | `cdn:zones:write` | POST/PATCH zones, BIND export | создать зону | | `cdn:sync:write` | POST `…/sync`, `…/apply` | Sync / Apply | | `cdn:topology:read` | GET `/api/v1/topology` | `/topology` | | `cdn:settings:admin` | GET/PATCH `/api/v1/settings` | `/settings/*` | Без app `cdn` в JWT `apps` → **403** на защищённые `/api/v1/*`. `AUTH_REQUIRED=false` — локальный login (`ADMIN_*`) для тестов/dev без portal; UI `/login`. ## App Switcher Публичный конфиг: `GET {AUTH_PORTAL_URL}/api/v1/app-switcher` (CORS open). CDNManager chrome (`AppSwitcher` / `AppsMenu`) читает его через `ensureAuthConfig().portalUrl`. Редактор только на портале: **Админка → Ссылки приложений** (`/admin/apps`). В CDNManager Settings → Интеграции — read-only ссылка на портал. `CURRENT_APP_ID = cdn`. Если в JWT есть `apps[]` — в меню только пересечение с каталогом. ## Audit ingest Dual-write локального журнала в portal: [`integrate-audit-ingest.md`](./integrate-audit-ingest.md) (`source_app: cdn`). ## UI аккаунта SidebarFooter → **NavUser**: Настройки, Тема, Выйти → `AUTH_PORTAL_URL/logout`. ## Logout (SSO) Очистить `cdnmanager_token` → редирект на **`/logout`** портала (не на `/?return_to=…` — иначе portal сразу выдаст новый SSO-токен). ## Production (Docker) Рекомендуется Traefik-стек в репозитории CDNManager: `docs/deploy-traefik.md` (`deploy/docker-compose.traefik.yml` + `deploy/env.traefik.example`). Ключевые env контейнера: ```env AUTH_REQUIRED=true AUTH_JWT_SECRET=<тот же JWT_SECRET портала> AUTH_ISSUER=https://auth.shnt.top AUTH_PORTAL_URL=https://auth.shnt.top AUTH_AUDIT_INGEST_SECRET= CLOUDFLARE_API_TOKEN= ``` В portal: `RETURN_TO_ALLOWLIST` включает `https://cdn.shnt.top` (или ваш origin). ## Troubleshooting | Симптом | Причина | |---------|---------| | SSO loop / «Сессия не принята» | разный `JWT_SECRET` или `ISSUER` у portal и CDNManager | | 403 «Нет доступа к приложению» | у пользователя нет app `cdn` в portal | | 403 «Недостаточно прав» | нет нужного `cdn:…` permission | | return_to rejected | origin CDNManager не в `RETURN_TO_ALLOWLIST` | | «Выйти» сразу возвращает в CDNManager | клиент должен открывать `/logout`, не login с `return_to` |