fix(mcp): подготовить доступ и отзыв разрешений для приёмки

This commit is contained in:
Denozordec committed 2026-11-03 03:05:24 +07:00
commit ef7bb79d4e
311 files changed
+73119

No files matched your search

+138
View File
@@ -0,0 +1,138 @@
# Интеграция auth-portal ↔ VPS Tracker
Единый вход: пользователь логинится на auth-portal, получает JWT, переходит в VPS Tracker с токеном в URL fragment. VPS API проверяет JWT и права `vps:*`.
## Архитектура
```
Browser → VPS UI (нет token)
→ redirect AUTH_PORTAL_URL/?return_to=http://localhost:5173/auth/callback
→ login
→ redirect return_to#access_token=…
→ VPS /auth/callback сохраняет token
→ API Authorization: Bearer …
```
Общий секрет: `JWT_SECRET` / `AUTH_JWT_SECRET` (HS256). Issuer: `ISSUER` / `AUTH_ISSUER`.
## Локальный запуск
### 1. auth-portal
```bash
cd auth-portal
pnpm install
cp .env.example .env
# JWT_SECRET=dev-secret-change-me
# RETURN_TO_ALLOWLIST=.shnt.top,localhost,http://localhost:5173
pnpm --filter @authportal/api dev # :8080
pnpm --filter web dev # :5175
```
В `apps/web/.env.local`:
```env
VITE_VPS_APP_URL=http://localhost:5173
```
Bootstrap: `admin@shnt.top` / `admin`. В админке выдайте app **vps** и нужные permissions.
### 2. vps-tracker
```bash
cd vps-tracker
pnpm install
```
Корень / API env (или shell):
```env
AUTH_REQUIRED=true
AUTH_JWT_SECRET=dev-secret-change-me
AUTH_ISSUER=https://auth.shnt.top
AUTH_PORTAL_URL=http://localhost:5175
```
`apps/web/.env.local`:
```env
VITE_AUTH_ENABLED=true
VITE_AUTH_PORTAL_URL=http://localhost:5175
```
```bash
pnpm --filter @cfdm/api dev
pnpm --filter web dev # :5173
```
Откройте http://localhost:5173 → редирект на portal → после логина обратно в VPS.
## Permissions ↔ API / UI
Иерархия: `admin` ⊃ `write` ⊃ `read` в рамках одной секции.
| Permission | API | UI |
|------------|-----|-----|
| `vps:dashboard:read` | `GET /api/dashboard/*` | `/dashboard` |
| `vps:vps:read` | `GET /api/vps`, projects, reports-related reads | `/vps`, `/tariffs`, `/projects`, аналитика |
| `vps:vps:write` | POST/PUT/PATCH/DELETE `/api/vps`, projects | create/edit/delete VPS |
| `vps:accounts:read` | GET providers, provider-accounts | `/providers`, `/accounts` |
| `vps:accounts:write` | мутации providers/accounts | формы аккаунтов |
| `vps:payments:read` | GET payments, balance-ledger | `/payments`, `/balance` |
| `vps:payments:write` | мутации payments / ledger | формы платежей |
| `vps:sync:write` | POST `/api/sync/*`, GET sync status | `/sync-journal`, кнопка sync |
| `vps:spaces:admin` | управление всеми пространствами, grants из main | space switcher (все spaces), share/assign |
| `vps:settings:admin` | `/api/settings`, backup, audit, migrate | `/settings`, `/audit`, `/spaces` |
## Пространства (spaces)
Данные VPS Tracker изолированы по `spaceId`. Заголовок **`X-Space-Id`**.
- **space-main** — основное (админский пул); существующие данные мигрируют сюда.
- **personal** — автосоздание `space-user-{userId}` при первом входе.
- **Share (ACL)** — `POST /api/spaces/:id/vps/:vpsId/share` — VPS остаётся в исходном space.
- **Assign** — `POST /api/spaces/:id/vps/:vpsId/assign` — перенос; `providerAccountId` сбрасывается.
- Участники: `GET/POST/PATCH/DELETE /api/spaces/:id/members` (userId из auth-portal).
- Env: `VPS_MAIN_SPACE_OWNER_USER_ID` — owner для main при bootstrap.
Без app `vps` в JWT `apps` → **403** на весь `/api/*` (кроме health и CFDM integration).
`AUTH_REQUIRED=false` — auth выключен (удобно для локальной разработки без portal); данные в `space-main`.
## App Switcher
Публичный конфиг: `GET {AUTH_PORTAL_URL}/api/v1/app-switcher`. VPS chrome читает его через `ensureAuthConfig().portalUrl`; offline fallback — defaults с ids `cfdm` | `vps` | `bgp`.
Редактор только на портале: **Админка → Ссылки приложений** (`/admin/apps`). В VPS Settings → Integrations — read-only ссылка.
`CURRENT_APP_ID = vps`. Фильтр меню по JWT `apps[]` при наличии claims.
## Audit ingest
Dual-write локального журнала в portal: [`integrate-audit-ingest.md`](./integrate-audit-ingest.md) (`source_app: vps`).
## Troubleshooting
| Симптом | Причина |
|---------|---------|
| 401 на API | Нет/битый Bearer; разные `JWT_SECRET` |
| 403 «нет доступа к приложению» | В portal не выдан app `vps` |
| 403 на write | Только `*:read` в permissions |
| Loop на login | `return_to` не в `RETURN_TO_ALLOWLIST` |
| Infinite SSO / 429 | Просроченный JWT в portal localStorage; или разный `JWT_SECRET`/`ISSUER`. Portal чистит expired token; VPS блокирует повторный handoff 12с |
| «Выйти» сразу возвращает в приложение | Старый клиент редиректил на `/?return_to=…` при живой portal-сессии. Нужен редирект на **`/logout`** (см. ниже) |
| CORS | Portal и VPS на разных origin — fragment handoff не требует CORS для token; public app-switcher GET тоже CORS-open |
## Logout (SSO)
«Выйти» в приложении: очистить локальный JWT → `AUTH_PORTAL_URL/logout` (без `return_to`).
Портал на `/logout`: `POST /api/v1/auth/logout` (revoke refresh cookie) → `clearToken()` → форма логина.
## Production
- Один `JWT_SECRET` в secret store обоих сервисов
- `ISSUER=https://auth.shnt.top`
- `RETURN_TO_ALLOWLIST=.shnt.top,https://vps.shnt.top`
- `AUTH_REQUIRED=true`, `VITE_AUTH_ENABLED=true`
- `VITE_VPS_APP_URL=https://vps.shnt.top` (portal)