Files
IdP/apps/docs/lib/docs-pages.ts
2026-06-25 08:31:36 +03:00

1045 lines
47 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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: 'callout',
variant: 'tip',
title: 'Production на сервере',
text: 'Для развёртывания на Linux с Docker, Nginx и SSL используйте скрипт install.sh — подробности в разделе «Развёртывание на сервере».'
},
{
type: 'code',
language: 'bash',
title: 'Сервер (рекомендуется)',
code: `git clone https://git.lendry.ru/lendry/IdP.git
cd IdP
chmod +x install.sh
./install.sh`
},
{
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: 'Автоматическая установка через install.sh: Docker, Nginx, SSL и отдельные домены для каждого сервиса.',
sections: [
{
id: 'install-script',
title: '1. Автоматическая установка (install.sh)',
blocks: [
{
type: 'paragraph',
text: 'Скрипт install.sh разворачивает весь стек одной командой: Docker, Nginx, генерация .env, reverse proxy. Поддерживает локальную сеть без интернета (--intranet), самоподписанный HTTPS и Let\'s Encrypt для публичных серверов. На Windows Server Nginx запускается в Docker-контейнере.'
},
{
type: 'callout',
variant: 'tip',
title: 'Локальная сеть (AD, .lpr, .local)',
text: 'Если домены только внутри LAN и Let\'s Encrypt недоступен — используйте ./install.sh --intranet --ssl none. Доступ: http://api.idpmvk.lpr без выхода в интернет.'
},
{
type: 'code',
language: 'bash',
title: 'Быстрый старт',
code: `git clone https://git.lendry.ru/lendry/IdP.git
cd IdP
chmod +x install.sh
./install.sh`
},
{
type: 'code',
language: 'bash',
title: 'Production без вопросов',
code: `./install.sh --install --yes \\
--api-domain api.id.example.ru \\
--frontend-domain id.example.ru \\
--docs-domain docs.example.ru \\
--email admin@example.ru`
},
{
type: 'code',
language: 'bash',
title: 'Установка без клонирования',
code: `curl -fsSL https://git.lendry.ru/lendry/IdP/raw/branch/main/install.sh | bash`
}
]
},
{
id: 'intranet-network',
title: '2. Локальная сеть (без интернета)',
blocks: [
{
type: 'paragraph',
text: 'Для корпоративной LAN, Windows Server и внутренних доменов (например idpmvk.lpr) не нужен доступ из интернета и Let\'s Encrypt. Скрипт настраивает Nginx на HTTP или HTTPS с самоподписанным сертификатом (openssl). На Windows Server Nginx работает в Docker (профиль proxy).'
},
{
type: 'code',
language: 'bash',
title: 'Рекомендуемая команда (HTTP)',
code: `./install.sh --intranet --install --yes --offline \\
--api-domain api.idpmvk.lpr \\
--frontend-domain id.idpmvk.lpr \\
--docs-domain docs.idpmvk.lpr \\
--ssl none`
},
{
type: 'code',
language: 'bash',
title: 'HTTPS самоподписанный (опционально)',
code: `./install.sh --intranet --install --yes --offline \\
--api-domain api.idpmvk.lpr \\
--frontend-domain id.idpmvk.lpr \\
--docs-domain docs.idpmvk.lpr \\
--ssl selfsigned`
},
{
type: 'table',
headers: ['SSL_TYPE', 'Описание', 'Интернет'],
rows: [
['none', 'http://api.idpmvk.lpr — без сертификатов', 'Не нужен'],
['selfsigned', 'https://... — openssl, предупреждение браузера', 'Не нужен'],
['letsencrypt', 'Публичный HTTPS', 'Нужен']
]
},
{
type: 'callout',
variant: 'warning',
title: 'DNS на клиентах',
text: 'Пропишите A-записи во внутреннем DNS Active Directory или файл hosts на каждом ПК: IP-сервера → api.idpmvk.lpr, id.idpmvk.lpr, docs.idpmvk.lpr. Windows: C:\\Windows\\System32\\drivers\\etc\\hosts'
},
{
type: 'code',
language: 'text',
title: 'Пример hosts (Windows / Linux)',
code: `192.168.1.10 api.idpmvk.lpr
192.168.1.10 id.idpmvk.lpr
192.168.1.10 docs.idpmvk.lpr`
},
{
type: 'callout',
variant: 'info',
title: 'Windows Server',
text: 'Запускайте install.sh из Git Bash или WSL. Docker Desktop / Docker Engine должен быть установлен заранее (--offline). Nginx поднимается контейнером на портах 80 и 443.'
}
]
},
{
id: 'server-requirements',
title: '3. Требования к серверу',
blocks: [
{
type: 'list',
items: [
'Ubuntu 22.04+ / Debian 12+ / Fedora / RHEL (Linux)',
'Минимум 4 GB RAM, 2 vCPU, 40 GB SSD',
'Отдельные домены или поддомены для API, Frontend и Docs (A-запись → IP сервера)',
'Открытые порты 80 и 443 (остальные сервисы доступны только через Nginx на localhost)',
'Права sudo/root для установки Docker и Nginx'
]
}
]
},
{
id: 'domains',
title: '4. Отдельные домены для сервисов',
blocks: [
{
type: 'paragraph',
text: 'При установке и при повторной настройке (--configure-domains) можно указать свой домен для каждого компонента. Текущие значения подставляются по умолчанию — достаточно изменить только нужные.'
},
{
type: 'table',
headers: ['Сервис', 'Параметр', 'Пример', 'Nginx → порт'],
rows: [
['API / Swagger', '--api-domain', 'api.id.example.ru', '127.0.0.1:3000'],
['Frontend (UI)', '--frontend-domain', 'id.example.ru', '127.0.0.1:3002'],
['Документация', '--docs-domain', 'docs.example.ru', '127.0.0.1:3003'],
['WebSocket', '--ws-domain', 'пусто = API + /ws', '127.0.0.1:8085'],
['MinIO S3', '--minio-domain', 'опционально', '127.0.0.1:9000'],
['MinIO Console', '--minio-console-domain', 'опционально', '127.0.0.1:9001']
]
},
{
type: 'callout',
variant: 'info',
title: 'WebSocket',
text: 'Если --ws-domain не указан, WebSocket проксируется на том же домене, что и API, по пути /ws (wss://api.example.ru/ws). Для отдельного домена укажите --ws-domain ws.example.ru.'
}
]
},
{
id: 'install-menu',
title: '5. Меню управления (повторный запуск)',
blocks: [
{
type: 'paragraph',
text: 'Если файл .env уже существует, повторный вызов ./install.sh открывает интерактивное меню — как у панелей с автоматическим reverse proxy.'
},
{
type: 'table',
headers: ['Пункт', 'Действие'],
rows: [
['1', 'Полная установка / переустановка стека'],
['2', 'Настроить или изменить домены, Nginx и SSL'],
['3', 'Обновить SSL-сертификаты (Let\'s Encrypt)'],
['4', 'Перезапуск / пересборка контейнеров'],
['5', 'Статус сервисов и проверка Nginx'],
['6', 'Удалить конфиги Nginx IdP'],
['0', 'Выход']
]
},
{
type: 'code',
language: 'bash',
title: 'Смена доменов после установки',
code: `# Интерактивно
./install.sh --configure-domains
# Или через меню
./install.sh
# → пункт 2
# Пример: вынести Swagger на отдельный домен
./install.sh --configure-domains \\
--api-domain swagger.id.example.ru \\
--frontend-domain id.example.ru \\
--docs-domain docs.example.ru`
}
]
},
{
id: 'install-commands',
title: '6. Команды и флаги',
blocks: [
{
type: 'table',
headers: ['Команда / флаг', 'Описание'],
rows: [
['./install.sh', 'Интерактивное меню (если .env есть) или первая установка'],
['./install.sh --install', 'Полная установка'],
['./install.sh --configure-domains', 'Изменить домены и пересобрать frontend/docs'],
['./install.sh --renew-ssl', 'Обновить сертификаты Let\'s Encrypt'],
['./install.sh --restart', 'docker compose up -d --build'],
['./install.sh --status', 'Статус контейнеров и доменов'],
['./install.sh --intranet', 'Локальная сеть, HTTP по умолчанию'],
['./install.sh --ssl none', 'Только HTTP через Nginx'],
['./install.sh --ssl selfsigned', 'HTTPS самоподписанный'],
['./install.sh --offline', 'Не скачивать Docker из интернета'],
['./install.sh --nginx-mode docker', 'Nginx в контейнере (Windows)'],
['./install.sh --local', 'localhost:3000/3002/3003 без Nginx'],
['./install.sh --yes', 'Без интерактивных вопросов'],
['FORCE_ENV=1 ./install.sh', 'Пересоздать .env с новыми секретами']
]
},
{
type: 'code',
language: 'bash',
title: 'Локальная разработка без доменов',
code: `./install.sh --local
# или
./install.sh --install --local --yes`
}
]
},
{
id: 'install-automation',
title: '7. Что делает скрипт автоматически',
blocks: [
{
type: 'list',
items: [
'Устанавливает curl, git, openssl, jq (apt/dnf)',
'Устанавливает Docker и Docker Compose v2 (get.docker.com)',
'Добавляет пользователя в группу docker',
'Генерирует .env: пароли PostgreSQL, RabbitMQ, MinIO, JWT-секреты, DATA_ENCRYPTION_KEY',
'Создаёт docker-compose.override.yml — привязка портов к 127.0.0.1',
'Устанавливает Nginx и Certbot, настраивает UFW (80, 443)',
'Генерирует конфиги /etc/nginx/sites-available/lendry-id-*.conf',
'Получает SSL через certbot (webroot), включает certbot.timer',
'Запускает docker compose up -d --build и ждёт health сервисов',
'При смене доменов пересобирает frontend и docs с новыми NEXT_PUBLIC_* URL'
]
},
{
type: 'callout',
variant: 'warning',
title: 'Секреты',
text: 'Файл .env не коммитьте в git. Сохраните его в надёжном месте после первой установки. FORCE_ENV=1 генерирует новые секреты и сбрасывает старые.'
}
]
},
{
id: 'clone-config',
title: '8. Переменные .env (генерируются автоматически)',
blocks: [
{
type: 'paragraph',
text: 'install.sh создаёт и обновляет .env на основе .env.example. При ручном редактировании не меняйте секреты без необходимости — для смены доменов используйте ./install.sh --configure-domains.'
},
{
type: 'code',
language: 'bash',
title: 'Ключевые переменные .env',
code: `# Домены (без протокола)
DOMAIN_API=api.id.example.ru
DOMAIN_FRONTEND=id.example.ru
DOMAIN_DOCS=docs.example.ru
DOMAIN_WS=
# Публичные URL (скрипт подставляет https://)
PUBLIC_API_URL=https://api.id.example.ru
PUBLIC_FRONTEND_URL=https://id.example.ru
PUBLIC_DOCS_URL=https://docs.example.ru
PUBLIC_WS_URL=wss://api.id.example.ru/ws
USE_NGINX_SSL=true
CERTBOT_EMAIL=admin@example.ru
INSTALL_MODE=production
# Секреты — генерируются install.sh
JWT_ACCESS_SECRET=...
JWT_REFRESH_SECRET=...
DATA_ENCRYPTION_KEY=...
POSTGRES_PASSWORD=...
RABBITMQ_DEFAULT_PASS=...
MINIO_ROOT_PASSWORD=...`
}
]
},
{
id: 'env-services',
title: '9. Переменные окружения сервисов',
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 (build args + runtime)'],
['docs', 'NEXT_PUBLIC_API_URL, INTERNAL_API_URL'],
['media-ws', 'REDIS_ADDR, RABBITMQ_URL, JWT_ACCESS_SECRET'],
['ldap-auth', 'PORT=8086']
]
}
]
},
{
id: 'after-install',
title: '10. После установки',
blocks: [
{
type: 'table',
headers: ['Ресурс', 'URL (production)'],
rows: [
['API / Swagger', 'https://<DOMAIN_API>/api'],
['Frontend', 'https://<DOMAIN_FRONTEND>'],
['Документация', 'https://<DOMAIN_DOCS>/docs'],
['WebSocket', 'wss://<DOMAIN_API>/ws или wss://<DOMAIN_WS>/ws'],
['MinIO Console', 'только localhost:9001 (если не задан домен)'],
['RabbitMQ UI', 'только localhost:15672']
]
},
{
type: 'callout',
variant: 'tip',
title: 'Первый пользователь',
text: 'Первый зарегистрированный пользователь автоматически становится супер-администратором. Название проекта (PROJECT_NAME) и LDAP настраиваются в админке: /admin/settings.'
},
{
type: 'code',
language: 'bash',
title: 'Полезные команды',
code: `docker compose ps
docker compose logs -f api-gateway sso-core
./install.sh --status
./install.sh --renew-ssl`
}
]
},
{
id: 'migrations',
title: '11. Миграции и 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: 'info',
title: 'SystemSetting',
text: 'Название проекта (PROJECT_NAME), tagline и LDAP-параметры настраиваются в админке. Frontend и docs подтягивают PROJECT_NAME через GET /settings/public.'
}
]
},
{
id: 'backup',
title: '12. Резервное копирование',
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: '13. Мониторинг и обновления',
blocks: [
{
type: 'list',
items: [
'Health: GET /health на api-gateway и sso-core',
'Логи: docker compose logs -f --tail=200',
'Обновление кода: git pull && ./install.sh --restart',
'Смена доменов: ./install.sh --configure-domains',
'SSL: ./install.sh --renew-ssl или автоматически через certbot.timer'
]
}
]
}
]
},
{
slug: 'authentication',
title: 'Аутентификация',
description: 'Способы входа: TOTP (Google Authenticator), OTP, пароль, LDAP, PIN и refresh-сессии.',
sections: [
{
id: 'flows',
title: 'Сценарии входа',
blocks: [
{
type: 'table',
headers: ['Сценарий', 'Endpoint', 'Описание'],
rows: [
['Identifier-first', 'POST /auth/identify', 'Проверка пользователя, isTotpEnabled, otpChannels и альтернативных способов'],
['TOTP (основной)', 'POST /auth/totp/begin + verify', 'Вход через Google Authenticator, если аутентификатор подключён'],
['Passwordless OTP (альтернатива)', 'POST /auth/otp/send + verify', '6-значный код на почту/телефон вместо TOTP'],
['Пароль', 'POST /auth/login/password', 'Альтернативный вход по паролю'],
['LDAP/LDAPS', 'POST /auth/ldap/login', 'Корпоративный вход (требует LDAP_ENABLED)'],
['PIN', 'POST /auth/pin/verify', 'Разблокировка сессии после входа с PIN']
]
}
]
},
{
id: 'totp-login',
title: 'Вход через приложение-аутентификатор (TOTP)',
blocks: [
{
type: 'paragraph',
text: 'Если пользователь подключил Google Authenticator (или аналог) в разделе «Безопасность», код из приложения становится основным способом входа вместо SMS/email OTP. SMS и email используются только как альтернатива через «Другой способ входа».'
},
{
type: 'list',
items: [
'1. POST /auth/identify с почтой или телефоном — в ответе isTotpEnabled: true и otpChannels (доступные каналы для альтернативного OTP).',
'2. POST /auth/totp/begin — создаёт challenge (totpChallengeToken, TTL 5 минут). SMS/email на этом шаге не отправляются.',
'3. Пользователь вводит 6-значный код из приложения-аутентификатора.',
'4. POST /auth/totp/verify с totpChallengeToken и code — выдаётся JWT и refresh token (далее при необходимости PIN).'
]
},
{
type: 'callout',
variant: 'tip',
title: 'TOTP и SMS/email — альтернативы',
text: 'Код из аутентификатора и код из SMS/email не комбинируются: это два разных способа пройти один и тот же шаг входа. После успешного OTP verify TOTP повторно не запрашивается.'
},
{
type: 'table',
headers: ['Ситуация', 'Поведение UI / API'],
rows: [
['Аутентификатор подключён', 'После identify сразу экран TOTP (totp/begin), без автоматической отправки SMS/email'],
['Альтернатива: код на почту/телефон', 'POST /auth/otp/send с channel: email или phone, затем POST /auth/otp/verify'],
['Привязаны и почта, и телефон', 'Пользователь выбирает канал (otpChannels) перед отправкой OTP'],
['Только почта или только телефон', 'OTP отправляется сразу на единственный доступный канал'],
['Альтернатива: пароль', 'POST /auth/login/password — без дополнительного TOTP'],
['Включён PIN', 'После TOTP или OTP — POST /auth/pin/verify для полной сессии']
]
},
{
type: 'code',
title: 'POST /auth/identify — фрагмент ответа',
language: 'json',
code: `{
"exists": true,
"hasPassword": true,
"isPinEnabled": false,
"isTotpEnabled": true,
"otpChannels": [
{ "channel": "email", "masked": "u***@example.com" },
{ "channel": "phone", "masked": "+7********42" }
],
"methods": [
{ "kind": "password", "channel": "password", "masked": "Пароль" }
]
}`
},
{
type: 'code',
title: 'Начать вход по TOTP',
language: 'bash',
code: `curl -X POST http://localhost:3000/auth/totp/begin \\
-H "Content-Type: application/json" \\
-d '{
"recipient": "user@example.com",
"fingerprint": "device-fingerprint-uuid",
"deviceName": "Chrome на Windows",
"deviceType": "WEB"
}'
# Ответ: { "totpChallengeToken": "eyJ..." }`
},
{
type: 'code',
title: 'Подтвердить код аутентификатора',
language: 'bash',
code: `curl -X POST http://localhost:3000/auth/totp/verify \\
-H "Content-Type: application/json" \\
-d '{
"totpChallengeToken": "eyJ...",
"code": "123456"
}'
# Ответ: AuthTokens (accessToken, refreshToken, sessionId, pinVerified, user)`
},
{
type: 'code',
title: 'Альтернатива: OTP на выбранный канал',
language: 'bash',
code: `# Отправить код (channel: email | phone | backupEmail | backupPhone)
curl -X POST http://localhost:3000/auth/otp/send \\
-H "Content-Type: application/json" \\
-d '{
"recipient": "user@example.com",
"channel": "phone"
}'
# Подтвердить OTP — сразу выдаёт сессию, без TOTP
curl -X POST http://localhost:3000/auth/otp/verify \\
-H "Content-Type: application/json" \\
-d '{
"recipient": "user@example.com",
"code": "654321",
"fingerprint": "device-fingerprint-uuid",
"deviceName": "Chrome на Windows",
"deviceType": "WEB"
}'`
}
]
},
{
id: 'totp-setup',
title: 'Подключение аутентификатора',
blocks: [
{
type: 'paragraph',
text: 'Настройка выполняется в личном кабинете: Безопасность → Дополнительная защита → Подключить аутентификатор. Название в Google Authenticator берётся из SystemSetting PROJECT_NAME (не захардкожено).'
},
{
type: 'list',
items: [
'GET /security/users/{userId}/totp/status — проверка, включён ли TOTP',
'POST /security/users/{userId}/totp/setup — QR-код (otpauthUrl) и секретный ключ',
'POST /security/users/{userId}/totp/enable — подтверждение первого кода и активация',
'POST /security/users/{userId}/totp/disable — отключение с проверкой текущего кода'
]
},
{
type: 'callout',
variant: 'info',
title: 'Повторная настройка',
text: 'Если настройка начата, но не завершена, повторный вызов setup возвращает тот же секрет — не нужно сканировать новый QR, пока не истёк незавершённый challenge.'
}
]
},
{
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: 'totp',
title: 'TOTP (Google Authenticator)',
blocks: [
{
type: 'paragraph',
text: 'Двухфакторная аутентификация через TOTP заменяет SMS/email OTP при входе. Управление устройствами и отзыв сессий — через те же endpoints, что описаны ниже.'
},
{
type: 'list',
items: [
'GET /security/users/{userId}/totp/status',
'POST /security/users/{userId}/totp/setup | enable | disable',
'POST /auth/totp/begin и POST /auth/totp/verify — вход с аутентификатором'
]
}
]
},
{
id: 'pin',
title: 'PIN-код',
blocks: [
{
type: 'paragraph',
text: 'PIN хранится как bcrypt hash. Таймаут блокировки читается из SystemSetting PIN_LOCK_TIMEOUT_MINUTES — значение не захардкожено во frontend.'
},
{
type: 'list',
items: [
'PIN_DELETE_GRACE_MINUTES — задержка перед окончательным удалением PIN после запроса',
'PIN_REQUIRE_ON_DELETE — требовать текущий PIN при запросе удаления защиты'
]
}
]
},
{
id: 'account-deletion',
title: 'Удаление аккаунта',
blocks: [
{
type: 'paragraph',
text: 'Пользователь может запланировать удаление профиля в личном кабинете: раздел «Данные» (/data) → «Удалить профиль». Аккаунт не удаляется мгновенно — начинается период ожидания, настраиваемый администратором.'
},
{
type: 'table',
headers: ['Ключ SystemSetting', 'Описание', 'По умолчанию'],
rows: [
['ACCOUNT_DELETE_GRACE_DAYS', 'Через сколько дней после запроса окончательно удалить аккаунт', '30']
]
},
{
type: 'list',
items: [
'POST /profile/users/{userId}/self-delete — запланировать удаление (только свой профиль)',
'GET /profile/users/{userId}/self-delete/status — дата окончательного удаления и статус pending',
'POST /profile/users/{userId}/self-delete/cancel — отменить запрос до истечения срока',
'До истечения срока пользователь может входить и пользоваться сервисом как обычно',
'На странице /data отображается предупреждение с датой удаления и кнопкой отмены'
]
},
{
type: 'callout',
variant: 'warning',
title: 'Что происходит при финализации',
text: 'Фоновый планировщик sso-core (каждые 5 минут) находит аккаунты с истёкшим сроком ожидания и выполняет soft-delete: анонимизация email/телефона/username, отзыв сессий, снятие ролей. Семьи, где пользователь — создатель, удаляются полностью (участники, чаты, медиа). Из остальных семей пользователь исключается.'
}
]
},
{
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/телефону/логину или через поиск в интерфейсе. Лимит участников задаётся в SystemSetting MAX_FAMILY_MEMBERS (по умолчанию 6). При создании семьи автоматически создаётся общий чат «Общий чат».'
},
{
type: 'list',
items: [
'POST /family/groups — создать семью',
'GET /family/users/{userId}/groups — список семей пользователя',
'PATCH /family/groups/{groupId} — переименовать (только участники, название — создатель)',
'POST /family/groups/{groupId}/invites — отправить приглашение',
'GET /family/groups/{groupId}/invite-search?q=... — поиск пользователей для приглашения'
]
}
]
},
{
id: 'family-leave',
title: 'Выход и исключение участников',
blocks: [
{
type: 'paragraph',
text: 'Участник может выйти из семьи самостоятельно; создатель семьи может исключить любого участника, кроме себя. При выходе или исключении пользователь удаляется из всех чатов этой семьи.'
},
{
type: 'list',
items: [
'DELETE /family/members/{memberId} — выход (если memberId свой) или исключение (если запрос от создателя семьи)',
'Создателя семьи (role: owner) удалить через этот endpoint нельзя — только удалить всю семью целиком'
]
}
]
},
{
id: 'family-delete',
title: 'Удаление семьи',
blocks: [
{
type: 'paragraph',
text: 'Только создатель семьи может полностью удалить группу. В интерфейсе: страница семьи → «Участники семьи» → «Удалить семью». Действие необратимо.'
},
{
type: 'list',
items: [
'DELETE /family/groups/{groupId} — удалить семью (только ownerId === текущий пользователь)',
'Все участники исключаются из семьи',
'Все приглашения (FamilyInvite) удаляются',
'Все чаты семьи (ChatRoom), сообщения, опросы и голоса удаляются каскадом',
'Медиа в MinIO (аватар семьи, аватары чатов, вложения сообщений) удаляются best-effort',
'Остальным участникам отправляется уведомление family_group_deleted и событие WebSocket'
]
},
{
type: 'callout',
variant: 'warning',
title: 'Без передачи владения',
text: 'Перед удалением семьи нельзя «передать» роль создателя другому участнику — только полное удаление группы или выход участников по отдельности.'
}
]
},
{
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',
'События realtime: chat_message, chat_message_updated, chat_message_deleted, family_group_deleted'
]
}
]
}
]
},
{
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);
}