Добавлен POST /api/v1/ingest/audit, фильтры source_app/user_id, last_login_at; таблица пользователей по solution-users-1 с журналом в Sheet. Co-authored-by: Cursor <[email protected]>
6.0 KiB
Интеграция 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
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:
VITE_VPS_APP_URL=http://localhost:5173
Bootstrap: [email protected] / admin. В админке выдайте app vps и нужные permissions.
2. vps-tracker
cd vps-tracker
pnpm install
Корень / API env (или shell):
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:
VITE_AUTH_ENABLED=true
VITE_AUTH_PORTAL_URL=http://localhost:5175
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 (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.topRETURN_TO_ALLOWLIST=.shnt.top,https://vps.shnt.topAUTH_REQUIRED=true,VITE_AUTH_ENABLED=trueVITE_VPS_APP_URL=https://vps.shnt.top(portal)