616 lines
22 KiB
TypeScript
616 lines
22 KiB
TypeScript
export type DocBlock =
|
||
| { type: 'paragraph'; text: string }
|
||
| { type: 'list'; items: string[] }
|
||
| { type: 'code'; language: string; code: string; title?: string }
|
||
| { type: 'callout'; variant: 'info' | 'warning' | 'tip'; title: string; text: string }
|
||
| { type: 'table'; headers: string[]; rows: string[][] }
|
||
| { type: 'oauth-examples' }
|
||
| { type: 'auth-login-examples' }
|
||
| { type: 'auth-ldap-examples' }
|
||
| { type: 'api-reference' };
|
||
|
||
export interface DocSection {
|
||
id: string;
|
||
title: string;
|
||
blocks: DocBlock[];
|
||
}
|
||
|
||
export interface DocPage {
|
||
slug: string;
|
||
title: string;
|
||
description: string;
|
||
sections: DocSection[];
|
||
}
|
||
|
||
export const docPages: DocPage[] = [
|
||
{
|
||
slug: 'getting-started',
|
||
title: 'Начало работы',
|
||
description: 'Обзор экосистемы Identity Provider, порты сервисов и быстрый локальный запуск.',
|
||
sections: [
|
||
{
|
||
id: 'overview',
|
||
title: 'Обзор',
|
||
blocks: [
|
||
{
|
||
type: 'paragraph',
|
||
text: 'Lendry ID — enterprise Identity Provider в стиле Yandex ID: единый аккаунт, OAuth 2.0, LDAP/LDAPS, семейные группы, чат, PIN-блокировка сессий и админ-панель. Монорепозиторий состоит из NestJS-микросервисов, Go-сервисов и Next.js-приложений.'
|
||
},
|
||
{
|
||
type: 'callout',
|
||
variant: 'info',
|
||
title: 'Домен',
|
||
text: 'Production-домен: id.lendry.ru. Локально API доступен на порту 3000, frontend — 3002, документация — 3003.'
|
||
}
|
||
]
|
||
},
|
||
{
|
||
id: 'requirements',
|
||
title: 'Требования',
|
||
blocks: [
|
||
{
|
||
type: 'list',
|
||
items: [
|
||
'Node.js 20.19+ и npm 10+',
|
||
'Docker и Docker Compose',
|
||
'PostgreSQL 16, Redis 7, RabbitMQ 3.13, MinIO',
|
||
'Go 1.23+ (для локальной сборки media-ws и ldap-auth)'
|
||
]
|
||
}
|
||
]
|
||
},
|
||
{
|
||
id: 'quick-start',
|
||
title: 'Быстрый запуск',
|
||
blocks: [
|
||
{
|
||
type: 'code',
|
||
language: 'bash',
|
||
title: 'Локально через Docker',
|
||
code: `git clone <repository-url> lendry-id
|
||
cd lendry-id
|
||
docker compose up -d --build
|
||
|
||
# Проверка
|
||
curl http://localhost:3000/health
|
||
curl http://localhost:3003/docs/getting-started`
|
||
},
|
||
{
|
||
type: 'table',
|
||
headers: ['Сервис', 'Порт', 'Назначение'],
|
||
rows: [
|
||
['api-gateway', '3000', 'REST API, Swagger'],
|
||
['sso-core', '3001 / 50051', 'Бизнес-логика, gRPC'],
|
||
['frontend', '3002', 'UI входа и профиля'],
|
||
['docs', '3003', 'Документация'],
|
||
['media-ws', '8085', 'WebSocket уведомлений и чата'],
|
||
['ldap-auth', '8086', 'LDAP/LDAPS аутентификация'],
|
||
['MinIO', '9000 / 9001', 'Хранилище медиа'],
|
||
['RabbitMQ', '5672 / 15672', 'Очереди событий']
|
||
]
|
||
}
|
||
]
|
||
},
|
||
{
|
||
id: 'first-user',
|
||
title: 'Первый пользователь',
|
||
blocks: [
|
||
{
|
||
type: 'paragraph',
|
||
text: 'Первый зарегистрированный пользователь автоматически получает права супер-администратора (isSuperAdmin). Race condition защищена Redis-lock и Serializable-транзакцией PostgreSQL.'
|
||
},
|
||
{
|
||
type: 'code',
|
||
language: 'bash',
|
||
title: 'Регистрация через API',
|
||
code: `curl -X POST http://localhost:3000/auth/register \\
|
||
-H "Content-Type: application/json" \\
|
||
-d '{"displayName":"Администратор","email":"admin@example.com","password":"SecurePass123"}'`
|
||
}
|
||
]
|
||
}
|
||
]
|
||
},
|
||
{
|
||
slug: 'architecture',
|
||
title: 'Архитектура',
|
||
description: 'Схема микросервисов, потоки данных и технологический стек.',
|
||
sections: [
|
||
{
|
||
id: 'stack',
|
||
title: 'Технологический стек',
|
||
blocks: [
|
||
{
|
||
type: 'table',
|
||
headers: ['Компонент', 'Технологии'],
|
||
rows: [
|
||
['sso-core', 'NestJS, Prisma 7+, PostgreSQL, gRPC'],
|
||
['api-gateway', 'NestJS, REST, Swagger'],
|
||
['frontend / docs', 'Next.js App Router, React, Tailwind, shadcn/ui'],
|
||
['media-ws', 'Go, Gorilla WebSocket, Redis, RabbitMQ'],
|
||
['ldap-auth', 'Go, go-ldap'],
|
||
['Инфраструктура', 'Docker, Redis, RabbitMQ, MinIO']
|
||
]
|
||
}
|
||
]
|
||
},
|
||
{
|
||
id: 'flow',
|
||
title: 'Поток запросов',
|
||
blocks: [
|
||
{
|
||
type: 'paragraph',
|
||
text: 'Клиент (браузер или стороннее приложение) обращается к api-gateway по REST. Gateway проксирует вызовы в sso-core через gRPC. Медиафайлы загружаются в MinIO через presigned URL. Realtime-события (уведомления, чат) доставляются через media-ws по WebSocket с JWT-авторизацией.'
|
||
},
|
||
{
|
||
type: 'list',
|
||
items: [
|
||
'JWT access + refresh tokens для сессий',
|
||
'PIN-код: при включении сессия создаётся с pinVerified=false',
|
||
'SystemSetting — динамические бизнес-правила без хардкода во frontend',
|
||
'LinkedAccount — привязка LDAP, Google, Yandex и других провайдеров'
|
||
]
|
||
}
|
||
]
|
||
}
|
||
]
|
||
},
|
||
{
|
||
slug: 'deployment',
|
||
title: 'Развёртывание на сервере',
|
||
description: 'Пошаговый гайд по production-развёртыванию на Linux-сервере с Docker, Nginx и TLS.',
|
||
sections: [
|
||
{
|
||
id: 'server-requirements',
|
||
title: '1. Требования к серверу',
|
||
blocks: [
|
||
{
|
||
type: 'list',
|
||
items: [
|
||
'Ubuntu 22.04+ / Debian 12+ / аналогичный Linux',
|
||
'Минимум 4 GB RAM, 2 vCPU, 40 GB SSD',
|
||
'Домен с DNS A-записью (например id.lendry.ru)',
|
||
'Открытые порты 80 и 443 (остальное — только localhost / internal network)'
|
||
]
|
||
}
|
||
]
|
||
},
|
||
{
|
||
id: 'install-docker',
|
||
title: '2. Установка Docker',
|
||
blocks: [
|
||
{
|
||
type: 'code',
|
||
language: 'bash',
|
||
code: `sudo apt update && sudo apt install -y ca-certificates curl gnupg
|
||
curl -fsSL https://get.docker.com | sudo sh
|
||
sudo usermod -aG docker $USER
|
||
docker compose version`
|
||
}
|
||
]
|
||
},
|
||
{
|
||
id: 'clone-config',
|
||
title: '3. Клонирование и переменные окружения',
|
||
blocks: [
|
||
{
|
||
type: 'paragraph',
|
||
text: 'Создайте файл .env в корне проекта с production-секретами. Никогда не коммитьте его в git.'
|
||
},
|
||
{
|
||
type: 'code',
|
||
language: 'bash',
|
||
title: '.env (пример)',
|
||
code: `# Секреты JWT и шифрования — сгенерируйте: openssl rand -base64 48
|
||
JWT_ACCESS_SECRET=your-long-access-secret
|
||
JWT_REFRESH_SECRET=your-long-refresh-secret
|
||
DATA_ENCRYPTION_KEY=your-32-byte-encryption-key-change-me
|
||
|
||
# PostgreSQL (можно оставить docker internal)
|
||
POSTGRES_USER=lendry
|
||
POSTGRES_PASSWORD=strong-db-password
|
||
POSTGRES_DB=lendry_id
|
||
|
||
# RabbitMQ
|
||
RABBITMQ_DEFAULT_USER=lendry
|
||
RABBITMQ_DEFAULT_PASS=strong-rmq-password
|
||
|
||
# MinIO
|
||
MINIO_ROOT_USER=minioadmin
|
||
MINIO_ROOT_PASSWORD=strong-minio-password`
|
||
},
|
||
{
|
||
type: 'callout',
|
||
variant: 'warning',
|
||
title: 'PUBLIC_API_URL',
|
||
text: 'В production задайте PUBLIC_API_URL=https://id.lendry.ru для sso-core и api-gateway, а также MINIO_PUBLIC_ENDPOINT=id.lendry.ru (или CDN-домен для MinIO).'
|
||
}
|
||
]
|
||
},
|
||
{
|
||
id: 'docker-compose-prod',
|
||
title: '4. Production docker-compose',
|
||
blocks: [
|
||
{
|
||
type: 'paragraph',
|
||
text: 'Для production рекомендуется не публиковать PostgreSQL, Redis и RabbitMQ наружу — оставьте только api-gateway (3000), frontend (3002) и docs (3003) за reverse proxy.'
|
||
},
|
||
{
|
||
type: 'code',
|
||
language: 'bash',
|
||
code: `export DOCKER_BUILDKIT=1
|
||
docker compose up -d --build
|
||
|
||
# Проверка health всех сервисов
|
||
docker compose ps
|
||
docker compose logs -f sso-core api-gateway`
|
||
}
|
||
]
|
||
},
|
||
{
|
||
id: 'nginx',
|
||
title: '5. Nginx + TLS (Let\'s Encrypt)',
|
||
blocks: [
|
||
{
|
||
type: 'code',
|
||
language: 'nginx',
|
||
title: '/etc/nginx/sites-available/id.lendry.ru',
|
||
code: `server {
|
||
listen 443 ssl http2;
|
||
server_name id.lendry.ru;
|
||
|
||
ssl_certificate /etc/letsencrypt/live/id.lendry.ru/fullchain.pem;
|
||
ssl_certificate_key /etc/letsencrypt/live/id.lendry.ru/privkey.pem;
|
||
|
||
# REST API + Swagger
|
||
location / {
|
||
proxy_pass http://127.0.0.1:3000;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
}
|
||
|
||
# Frontend UI
|
||
location /app/ {
|
||
proxy_pass http://127.0.0.1:3002/;
|
||
proxy_set_header Host $host;
|
||
}
|
||
|
||
# WebSocket
|
||
location /ws {
|
||
proxy_pass http://127.0.0.1:8085;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Upgrade $http_upgrade;
|
||
proxy_set_header Connection "upgrade";
|
||
proxy_set_header Host $host;
|
||
}
|
||
}
|
||
|
||
# Документация на docs.id.lendry.ru или /docs-proxy
|
||
server {
|
||
listen 443 ssl http2;
|
||
server_name docs.id.lendry.ru;
|
||
ssl_certificate /etc/letsencrypt/live/docs.id.lendry.ru/fullchain.pem;
|
||
ssl_certificate_key /etc/letsencrypt/live/docs.id.lendry.ru/privkey.pem;
|
||
|
||
location / {
|
||
proxy_pass http://127.0.0.1:3003;
|
||
proxy_set_header Host $host;
|
||
}
|
||
}`
|
||
},
|
||
{
|
||
type: 'code',
|
||
language: 'bash',
|
||
code: `sudo certbot --nginx -d id.lendry.ru -d docs.id.lendry.ru
|
||
sudo nginx -t && sudo systemctl reload nginx`
|
||
}
|
||
]
|
||
},
|
||
{
|
||
id: 'env-services',
|
||
title: '6. Переменные окружения сервисов',
|
||
blocks: [
|
||
{
|
||
type: 'table',
|
||
headers: ['Сервис', 'Ключевые переменные'],
|
||
rows: [
|
||
['sso-core', 'DATABASE_URL, JWT_*_SECRET, DATA_ENCRYPTION_KEY, REDIS_URL, RABBITMQ_URL, MINIO_*, LDAP_AUTH_URL, PUBLIC_API_URL'],
|
||
['api-gateway', 'SSO_CORE_GRPC_URL, JWT_ACCESS_SECRET, PUBLIC_API_URL, MINIO_*'],
|
||
['frontend', 'NEXT_PUBLIC_API_URL, NEXT_PUBLIC_WS_URL'],
|
||
['docs', 'NEXT_PUBLIC_API_URL'],
|
||
['media-ws', 'REDIS_ADDR, RABBITMQ_URL, JWT_ACCESS_SECRET'],
|
||
['ldap-auth', 'PORT=8086']
|
||
]
|
||
}
|
||
]
|
||
},
|
||
{
|
||
id: 'migrations',
|
||
title: '7. Миграции и seed',
|
||
blocks: [
|
||
{
|
||
type: 'code',
|
||
language: 'bash',
|
||
code: `# Prisma migrate внутри контейнера sso-core
|
||
docker compose exec sso-core npx prisma migrate deploy
|
||
|
||
# Seed системных настроек (PROJECT_NAME, LDAP_*, PIN_*)
|
||
docker compose restart sso-core`
|
||
},
|
||
{
|
||
type: 'callout',
|
||
variant: 'tip',
|
||
title: 'SystemSetting',
|
||
text: 'Название проекта (PROJECT_NAME), tagline и LDAP-параметры настраиваются в админке: /admin/settings. Frontend и docs подтягивают PROJECT_NAME через GET /settings/public.'
|
||
}
|
||
]
|
||
},
|
||
{
|
||
id: 'backup',
|
||
title: '8. Резервное копирование',
|
||
blocks: [
|
||
{
|
||
type: 'list',
|
||
items: [
|
||
'PostgreSQL: pg_dump lendry_id ежедневно',
|
||
'MinIO: mc mirror или snapshot volume minio_data',
|
||
'Redis: RDB/AOF snapshots при необходимости сохранения сессий',
|
||
'Секреты .env — хранить в vault, не в репозитории'
|
||
]
|
||
},
|
||
{
|
||
type: 'code',
|
||
language: 'bash',
|
||
code: `docker compose exec postgres pg_dump -U lendry lendry_id > backup-$(date +%F).sql`
|
||
}
|
||
]
|
||
},
|
||
{
|
||
id: 'monitoring',
|
||
title: '9. Мониторинг и обновления',
|
||
blocks: [
|
||
{
|
||
type: 'list',
|
||
items: [
|
||
'Health: GET /health на api-gateway и sso-core',
|
||
'Логи: docker compose logs -f --tail=200',
|
||
'Обновление: git pull && docker compose up -d --build',
|
||
'Zero-downtime: blue-green или rolling update через orchestrator (K8s / Swarm)'
|
||
]
|
||
}
|
||
]
|
||
}
|
||
]
|
||
},
|
||
{
|
||
slug: 'authentication',
|
||
title: 'Аутентификация',
|
||
description: 'Способы входа: пароль, OTP, LDAP, PIN и refresh-сессии.',
|
||
sections: [
|
||
{
|
||
id: 'flows',
|
||
title: 'Сценарии входа',
|
||
blocks: [
|
||
{
|
||
type: 'table',
|
||
headers: ['Сценарий', 'Endpoint', 'Описание'],
|
||
rows: [
|
||
['Identifier-first', 'POST /auth/identify', 'Проверка существования пользователя и способа входа'],
|
||
['Passwordless OTP', 'POST /auth/otp/send + verify', '6-значный код на почту/телефон'],
|
||
['Пароль', 'POST /auth/login/password', 'Логин + пароль или tempAuthToken после OTP'],
|
||
['LDAP/LDAPS', 'POST /auth/ldap/login', 'Корпоративный вход (требует LDAP_ENABLED)'],
|
||
['PIN', 'POST /auth/pin/verify', 'Разблокировка сессии после входа с PIN']
|
||
]
|
||
}
|
||
]
|
||
},
|
||
{
|
||
id: 'login-example',
|
||
title: 'Примеры входа по паролю',
|
||
blocks: [
|
||
{
|
||
type: 'paragraph',
|
||
text: 'Endpoint POST /auth/login/password принимает login (почта, телефон или username), password и метаданные устройства. Ниже — готовые примеры на разных языках.'
|
||
},
|
||
{ type: 'auth-login-examples' },
|
||
{
|
||
type: 'callout',
|
||
variant: 'info',
|
||
title: 'PIN-блокировка',
|
||
text: 'Если у пользователя включён PIN, ответ содержит requiresPin=true и ограниченную сессию. Полный JWT выдаётся после POST /auth/pin/verify.'
|
||
}
|
||
]
|
||
}
|
||
]
|
||
},
|
||
{
|
||
slug: 'oauth',
|
||
title: 'OAuth 2.0',
|
||
description: 'Регистрация приложения, Authorization Code Flow, scopes и примеры на разных языках.',
|
||
sections: [
|
||
{
|
||
id: 'register-app',
|
||
title: 'Регистрация OAuth-приложения',
|
||
blocks: [
|
||
{
|
||
type: 'list',
|
||
items: [
|
||
'Войдите как супер-администратор в админ-панель',
|
||
'Перейдите в RBAC → OAuth приложения',
|
||
'Создайте клиент: name, redirectUris, scopes (openid, profile, email)',
|
||
'Confidential-клиент получит client_secret один раз — сохраните его'
|
||
]
|
||
}
|
||
]
|
||
},
|
||
{
|
||
id: 'flow',
|
||
title: 'Authorization Code Flow',
|
||
blocks: [
|
||
{
|
||
type: 'paragraph',
|
||
text: '1) Пользователь авторизуется в Lendry ID. 2) Ваше приложение перенаправляет на GET /oauth/authorize с clientId, redirectUri, scope, userId. 3) IdP возвращает redirectUrl с authorization code. 4) Backend обменивает code на токены через POST /oauth/token. 5) GET /oauth/userinfo возвращает профиль.'
|
||
},
|
||
{
|
||
type: 'callout',
|
||
variant: 'warning',
|
||
title: 'Безопасность',
|
||
text: 'client_secret никогда не храните во frontend. Обмен code → token выполняйте только на сервере.'
|
||
}
|
||
]
|
||
},
|
||
{
|
||
id: 'examples',
|
||
title: 'Примеры интеграции',
|
||
blocks: [{ type: 'oauth-examples' }]
|
||
},
|
||
{
|
||
id: 'scopes',
|
||
title: 'Scopes',
|
||
blocks: [
|
||
{
|
||
type: 'table',
|
||
headers: ['Scope', 'Доступ'],
|
||
rows: [
|
||
['openid', 'Идентификатор пользователя (sub)'],
|
||
['profile', 'displayName, avatar, bio'],
|
||
['email', 'Основная и резервная почта'],
|
||
['phone', 'Телефоны пользователя']
|
||
]
|
||
}
|
||
]
|
||
}
|
||
]
|
||
},
|
||
{
|
||
slug: 'ldap',
|
||
title: 'LDAP / LDAPS',
|
||
description: 'Корпоративный вход через Active Directory.',
|
||
sections: [
|
||
{
|
||
id: 'setup',
|
||
title: 'Настройка в админке',
|
||
blocks: [
|
||
{
|
||
type: 'table',
|
||
headers: ['Ключ SystemSetting', 'Описание'],
|
||
rows: [
|
||
['LDAP_ENABLED', 'Включить кнопку «Войти через LDAP»'],
|
||
['LDAP_USE_LDAPS', 'LDAPS на порту 636'],
|
||
['LDAP_HOST', 'DC-1.domain.local,DC-2.domain.local'],
|
||
['LDAP_BIND_USERNAME', 'Логин сервисной УЗ (mvkadmin)'],
|
||
['LDAP_BASE_DN', 'OU=Users,DC=domain,DC=local'],
|
||
['LDAP_BIND_PASSWORD', 'Пароль сервисной УЗ']
|
||
]
|
||
}
|
||
]
|
||
},
|
||
{
|
||
id: 'login',
|
||
title: 'Примеры LDAP-входа',
|
||
blocks: [
|
||
{
|
||
type: 'paragraph',
|
||
text: 'Endpoint POST /auth/ldap/login доступен при включённой настройке LDAP_ENABLED.'
|
||
},
|
||
{ type: 'auth-ldap-examples' }
|
||
]
|
||
}
|
||
]
|
||
},
|
||
{
|
||
slug: 'sessions',
|
||
title: 'Сессии и PIN',
|
||
description: 'Управление устройствами, PIN-блокировка и отзыв сессий.',
|
||
sections: [
|
||
{
|
||
id: 'pin',
|
||
title: 'PIN-код',
|
||
blocks: [
|
||
{
|
||
type: 'paragraph',
|
||
text: 'PIN хранится как bcrypt hash. Таймаут блокировки читается из SystemSetting PIN_LOCK_TIMEOUT_MINUTES — значение не захардкожено во frontend.'
|
||
}
|
||
]
|
||
},
|
||
{
|
||
id: 'devices',
|
||
title: 'Устройства и сессии',
|
||
blocks: [
|
||
{
|
||
type: 'list',
|
||
items: [
|
||
'GET /security/users/{userId}/devices — список устройств',
|
||
'POST /security/users/{userId}/sessions/{sessionId}/revoke — выход с устройства',
|
||
'POST /security/users/{userId}/revoke-all-sessions — выход везде'
|
||
]
|
||
}
|
||
]
|
||
}
|
||
]
|
||
},
|
||
{
|
||
slug: 'family-chat',
|
||
title: 'Семья и чат',
|
||
description: 'Семейные группы, приглашения, чат и realtime через WebSocket.',
|
||
sections: [
|
||
{
|
||
id: 'family',
|
||
title: 'Семейные группы',
|
||
blocks: [
|
||
{
|
||
type: 'paragraph',
|
||
text: 'Пользователь создаёт семью, приглашает участников по email/телефону/логину. Лимиты (max family members) берутся из SystemSetting.'
|
||
}
|
||
]
|
||
},
|
||
{
|
||
id: 'chat',
|
||
title: 'Чат и WebSocket',
|
||
blocks: [
|
||
{
|
||
type: 'list',
|
||
items: [
|
||
'REST: /chat/groups/{groupId}/rooms, /chat/rooms/{roomId}/messages',
|
||
'WebSocket: ws://localhost:8085/ws с JWT в query или заголовке',
|
||
'Медиа чата: presigned upload + защищённый stream с Authorization'
|
||
]
|
||
}
|
||
]
|
||
}
|
||
]
|
||
},
|
||
{
|
||
slug: 'api-reference',
|
||
title: 'Справочник API',
|
||
description: 'Полный список REST endpoints Lendry ID API. Swagger: /api на api-gateway.',
|
||
sections: [
|
||
{
|
||
id: 'endpoints',
|
||
title: 'Endpoints',
|
||
blocks: [{ type: 'api-reference' }]
|
||
},
|
||
{
|
||
id: 'swagger',
|
||
title: 'OpenAPI / Swagger',
|
||
blocks: [
|
||
{
|
||
type: 'paragraph',
|
||
text: 'Интерактивная документация доступна по адресу http://localhost:3000/api (Swagger UI). Все DTO и схемы генерируются автоматически из NestJS decorators.'
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
];
|
||
|
||
export function getDocPage(slug: string): DocPage | undefined {
|
||
return docPages.find((page) => page.slug === slug);
|
||
}
|
||
|
||
export function getAllDocSlugs() {
|
||
return docPages.map((page) => page.slug);
|
||
}
|