First Commit
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
# Интеграция 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: `[email protected]` / `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:settings:admin` | `/api/settings`, backup, audit, migrate | `/settings`, `/audit` |
|
||||
|
||||
Без app `vps` в JWT `apps` → **403** на весь `/api/*` (кроме health и CFDM integration).
|
||||
|
||||
`AUTH_REQUIRED=false` — auth выключен (удобно для локальной разработки без portal).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Симптом | Причина |
|
||||
|---------|---------|
|
||||
| 401 на API | Нет/битый Bearer; разные `JWT_SECRET` |
|
||||
| 403 «нет доступа к приложению» | В portal не выдан app `vps` |
|
||||
| 403 на write | Только `*:read` в permissions |
|
||||
| Loop на login | `return_to` не в `RETURN_TO_ALLOWLIST` |
|
||||
| CORS | Portal и VPS на разных origin — fragment handoff не требует CORS для token |
|
||||
|
||||
## 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)
|
||||
@@ -0,0 +1,41 @@
|
||||
# UI Design Contract — Auth Portal
|
||||
|
||||
Surface lock: **`frame`** (ReUI Frame). Не смешивать shadcn Card и Frame на ops-экранах.
|
||||
|
||||
Эталоны: [app-shell-12](https://reui.io/preview/base/app-shell-12) · [solution-users-1](https://reui.io/preview/base/solution-users-1) · [solution-users-2](https://reui.io/preview/base/solution-users-2) · [data-grid-filtering-2](https://reui.io/preview/base/data-grid-filtering-2) · [empty-state-12](https://reui.io/preview/base/empty-state-12)
|
||||
|
||||
## Shared App Shell chrome
|
||||
|
||||
Эталон: CFDM / EvoBGP + ReUI [app-shell-12](https://reui.io/preview/base/app-shell-12).
|
||||
|
||||
При переключении между portal / CFDM / EvoBGP / vps-tracker меняются **только** sidebar nav labels/hrefs и `main` content. Разметка, ширина, фон и hover chrome идентичны.
|
||||
|
||||
| Токен / зона | Значение |
|
||||
|--------------|----------|
|
||||
| `--sidebar-width` | `240px` |
|
||||
| Sidebar / hover colors | theme `--sidebar` / `--sidebar-accent` — **без** AppShell `color-mix` override |
|
||||
| Header | `h-12`, `sticky`, `border-b`, `px-4 md:px-6` |
|
||||
| Header left | `SidebarTrigger` + `Separator` + Breadcrumb |
|
||||
| Header right | **AppsMenu** → **ModeToggle** (+ account menu; SystemMonitor — stub/нет) |
|
||||
| Sidebar | AppSwitcher → groups → icons `size-4` → **пустой** `SidebarFooter` |
|
||||
| `main` | `gap-4 md:gap-6`, `px-4 py-4 md:px-6 md:py-5` |
|
||||
|
||||
Запрещено в chrome: `SidebarRail`, `NavUser` footer, Search pill в header.
|
||||
|
||||
Nav groups Auth Portal:
|
||||
|
||||
- **Портал:** Приложения (`/apps`)
|
||||
- **Админ** (только `is_admin`): Пользователи (`/admin`)
|
||||
|
||||
## Spacing
|
||||
|
||||
- AppShell main / PageShell: `gap-4 md:gap-6`
|
||||
- No `space-y-*` / `space-x-*` — use `flex` + `gap-*`
|
||||
- Max 1 primary CTA per screen
|
||||
- Semantic tokens only (`Badge` / `Alert` variants) — no raw `bg-emerald-*`
|
||||
|
||||
## Forbidden
|
||||
|
||||
- Card as ops list/dashboard shell
|
||||
- Hand-rolled data tables when ReUI DataGrid exists
|
||||
- Mixing Card and Frame on the same ops screen
|
||||
Reference in New Issue
Block a user