# Telegram Bot Telegram-бот — **второй канал доставки** (presentation-адаптер) поверх той же Application-логики, что и REST API. Он не содержит бизнес-правил: команды бота вызывают те же CQRS-команды/запросы (`ICommand/IQuery`), что и веб. Бизнес-инварианты живут в домене, а не в обработчиках бота. ## Возможности 1. **Ссылка на сайт** — кнопка/команда, открывающая веб-панель (при желании — с одноразовым deep-link авто-входом для уже привязанного пользователя). 2. **Мои конфиги** — список VPN-конфигов пользователя (протокол, локация, трафик, срок, статус), ссылка-подписка и QR по каждому. Доступно только привязанному аккаунту. 3. **Авторизация через Telegram (passwordless)** — вход на сайт без пароля: подтверждение входа в боте. Требует предварительной **привязки Telegram** к аккаунту. 4. **Админ: обработка запросов активации** — админ (по Telegram id из env) получает уведомление о запросе активации с комментарием заявителя и жмёт «Активировать / Отклонить» прямо в боте. 5. **DM-уведомления пользователю** — если Telegram привязан, бот шлёт личные уведомления о ключевых событиях: «аккаунт активирован», «конфиг отозван админом», «вы заблокированы». 6. **Восстановление пароля** — если пароль забыт, привязанный пользователь через бота получает одноразовую ссылку на страницу задания нового пароля (или входит passwordless и меняет пароль в настройках). Без привязки Telegram восстановление делает только админ. > **Скоуп бота в MVP — просмотр (read-only) по конфигам.** Создание/ротация/отзыв конфигов — только > на сайте. Полное самообслуживание в боте (создание/отзыв) — в backlog. ## Размещение в архитектуре - Бот работает **в том же процессе**, что и API, как `BackgroundService` (`TelegramBotHostedService`) — это укладывается в требование «фронт+бек в одном контейнере». - Транспорт с Telegram: **long polling** для MVP (не требует публичного webhook-URL, проще в одиночном контейнере). Webhook — опциональная альтернатива для прод-нагрузки (тогда — секретный токен заголовка для верификации). - Библиотека — **Telegram.Bot** (де-факто стандарт для C#). - Код бота лежит в `PnvPanel.Api/Telegram/` (хендлеры апдейтов, построители клавиатур, форматтеры сообщений). Обращения к домену — **только** через собственный `ISender`. `Telegram.Bot` не проникает в Application/Domain. ``` Telegram ──updates──► TelegramBotHostedService (Api) │ ISender.Send(command/query) // свой диспетчер ▼ Application (те же хендлеры, что и REST) ``` ## Модель данных (добавления) - `AppUser.TelegramUserId : long?` — id пользователя Telegram (уникальный, nullable до привязки). - `AppUser.TelegramUsername : string?`, `AppUser.TelegramLinkedAt : DateTimeOffset?`. - `TelegramLinkToken` — короткоживущий одноразовый токен привязки (`token`, `userId`, `expiresAt`, `consumedAt`). - `TelegramLoginRequest` — запрос passwordless-входа: `id/nonce`, `status` (`Pending/Approved/Rejected/Expired/Consumed`), `userId?` (после подтверждения), `createdAt`, `expiresAt`. Подробности полей — в [domain-model.md](domain-model.md). ## Флоу 1 — Привязка Telegram к аккаунту Предусловие: пользователь уже вошёл на сайте (изначально аккаунт создаётся с username+пароль). 1. На сайте «Привязать Telegram» → `POST /api/auth/telegram/link-token` → `{ deepLink }` вида `https://t.me/?start=link_` (+ QR). Токен короткоживущий, одноразовый. 2. Пользователь открывает бота по ссылке → `/start link_`. 3. Бот берёт `from.id` (Telegram user id), валидирует токен (`LinkTelegramCommand`), проставляет `TelegramUserId`/`TelegramUsername`/`TelegramLinkedAt`, гасит токен. 4. Бот подтверждает: «Аккаунт привязан». Сайт узнаёт об успехе (поллинг статуса или SignalR). Инварианты: один `TelegramUserId` ↔ один аккаунт; повторная привязка требует отвязки; токен нельзя переиспользовать и он истекает. ## Флоу 2 — Passwordless-вход через бота Предусловие: Telegram уже привязан к аккаунту. 1. На сайте «Войти через Telegram» → `POST /api/auth/telegram/login-request` → `{ requestId, deepLink, qr, expiresAt }`. Сайт начинает ждать результат (поллинг `GET /api/auth/telegram/login-request/{requestId}` или событие SignalR). 2. Пользователь открывает `https://t.me/?start=login_` → бот по `from.id` находит привязанный аккаунт и показывает inline-кнопки **«Подтвердить вход / Отклонить»** (с деталями: время, IP/устройство инициатора — для защиты от фишинга). 3. Подтверждение (`ApproveTelegramLoginCommand`) → запрос переходит в `Approved`, привязывается к `userId`. 4. Сайт (по поллингу/SignalR) получает результат: бэкенд выпускает **стандартные JWT** — access в теле ответа, refresh в httpOnly cookie. Запрос помечается `Consumed`. Если Telegram **не привязан** — passwordless-вход невозможен (бот предлагает сперва привязать аккаунт). Регистрация целиком через Telegram — вне MVP (см. backlog). ## Флоу 3 — Просмотр конфигов в боте 1. Привязанный пользователь: `/configs` или кнопка «Мои конфиги». 2. Бот вызывает `GetMyConfigsQuery` (тот же, что и веб) от имени `AppUser`, найденного по `TelegramUserId`. 3. Ответ — список с трафиком/сроком/статусом; по каждому конфигу — inline-кнопки «Ссылка», «QR». QR отдаётся как изображение (генерация на сервере). ## Флоу 4 — Обработка активации админом в боте 1. Пользователь отправляет запрос активации (сайт: `POST /api/activation/request { comment }`); доменное событие `ActivationRequested`. 2. Бот шлёт сообщение каждому админу (Telegram id из `AdminSeed__TelegramUserIds`) с username/комментарием заявителя и inline-кнопками **«✅ Активировать / ❌ Отклонить»**. 3. Нажатие → `ApproveActivationCommand`/`RejectActivationCommand` (те же, что на сайте) → пользователь активируется, ему уходит realtime-пуш `userActivated`, админам обновляется сообщение (решение зафиксировано). 4. Действие доступно только Telegram id из списка админов; проверка — на стороне бота перед вызовом команды. ## Команды и клавиатуры | Команда / кнопка | Действие | Требует привязки | | ---------------------- | -------------------------------------------------------------- | ---------------- | | `/start` | Приветствие + меню (Открыть сайт / Мои конфиги / Войти) | нет | | `/start link_` | Привязка аккаунта по токену | нет | | `/start login_` | Подтверждение passwordless-входа | да | | «Открыть сайт» | Ссылка на веб-панель (опц. одноразовый auto-login deep link) | нет / да | | `/configs` | Список конфигов | да | | `/login` | Инициировать/подтвердить вход | да | | `/resetpassword` | Одноразовая ссылка на смену пароля (восстановление) | да | | `/unlink` | Отвязать Telegram от аккаунта | да | | `/help` | Справка | нет | | «Активировать/Отклонить» | (admin) решение по запросу активации | админ по env | | `/requests` | (admin) список ожидающих запросов активации | админ по env | ## Безопасность - Токены привязки и nonce входа: высокоэнтропийные, **короткоживущие** (≈2–5 мин), **одноразовые**. - Подтверждение входа показывает контекст (время/устройство) — защита от несанкционированных запросов. - Верификация источника апдейтов: webhook — секретный заголовок; long polling — прямой канал к Bot API по TLS. - Rate-limiting на создание login/link-запросов и на команды бота. - Токен бота — секрет (env/secret-store), в логи не попадает; апдейты логируются без чувствительных данных. - Passwordless-вход выпускает те же JWT/refresh, что и обычный — единые правила сессий и ротации. - Альтернатива боту для веб-входа — официальный **Telegram Login Widget** (HMAC-подпись данных ботом, верификация на бэке). Оставлено как опция; основной путь — подтверждение в боте. ## Конфигурация ```jsonc "Telegram": { "BotToken": "…", // секрет (env/secret-store) "BotUsername": "PnvPanelBot", "Mode": "LongPolling", // или "Webhook" "WebhookUrl": null, "WebhookSecret": null, "PublicSiteUrl": "https://panel.example.com" } ``` Telegram id администраторов задаются отдельно — `AdminSeed__TelegramUserIds` (см. [`.env.example`](../.env.example)); именно они авторизуют админ-кнопки в боте и получают уведомления о запросах активации. Сообщения бота локализованы (**RU/EN**) по языку пользователя, синхронно с настройкой языка в вебе. Строго типизированные `IOptions` с валидацией на старте; при отсутствии `BotToken` бот не стартует (панель работает без него).