Files
PnvPanel/docs/telegram-bot.md
T
Leonid Pershin ed07221ca5
CI / Backend (build + test) (push) Failing after 1m35s
CI / Frontend (lint + typecheck + build) (push) Successful in 35s
Enhance Docker setup and user notification features
- Updated docker-compose.yml to include environment variables and health checks for the app service.
- Modified Dockerfile to install curl for health checks and adjusted the build process for backend services.
- Improved user notification handling in activation, blocking, and config revocation commands by integrating Telegram notifications.
- Added new test cases to validate the updated command handlers and Telegram notifier functionality.
- Enhanced documentation to reflect the new Telegram bot features and user management improvements.
2026-07-02 01:16:53 +03:00

13 KiB
Raw Blame History

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-вход: привязанный пользователь входит через бота (/login) и меняет пароль в настройках (ChangePasswordCommand). Без привязки Telegram сброс делает только админ (ResetUserPasswordCommand). Отдельная команда бота /resetpassword с одноразовой ссылкой на смену пароля — backlog, в MVP не реализована.

Скоуп бота в 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.

Флоу 1 — Привязка Telegram к аккаунту

Предусловие: пользователь уже вошёл на сайте (изначально аккаунт создаётся с username+пароль).

  1. На сайте «Привязать Telegram» → POST /api/auth/telegram/link-token{ deepLink } вида https://t.me/<bot>?start=link_<token> (+ QR). Токен короткоживущий, одноразовый.
  2. Пользователь открывает бота по ссылке → /start link_<token>.
  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/<bot>?start=login_<nonce> → бот по 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_<t> Привязка аккаунта по токену нет
/start login_<n> Подтверждение passwordless-входа (deep-link с сайта) да
/configs Список конфигов да
/unlink Отвязать Telegram от аккаунта да
/help Справка нет
«Активировать/Отклонить» (admin) решение по запросу активации админ по env
/requests (admin) список ожидающих запросов активации админ по env

Passwordless-вход и /resetpassword инициируются с сайта (кнопка «Войти через Telegram»), не отдельной командой бота — см. пункт 6 выше про /resetpassword (backlog).

Безопасность

  • Токены привязки и nonce входа: высокоэнтропийные, короткоживущие (≈25 мин), одноразовые.
  • Подтверждение входа показывает контекст (время/устройство) — защита от несанкционированных запросов.
  • Верификация источника апдейтов: webhook — секретный заголовок; long polling — прямой канал к Bot API по TLS.
  • Rate-limiting на создание login/link-запросов и на команды бота.
  • Токен бота — секрет (env/secret-store), в логи не попадает; апдейты логируются без чувствительных данных.
  • Passwordless-вход выпускает те же JWT/refresh, что и обычный — единые правила сессий и ротации.
  • Альтернатива боту для веб-входа — официальный Telegram Login Widget (HMAC-подпись данных ботом, верификация на бэке). Оставлено как опция; основной путь — подтверждение в боте.

Конфигурация

"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); именно они авторизуют админ-кнопки в боте и получают уведомления о запросах активации.

Сообщения бота локализованы (RU/EN) по языку пользователя, синхронно с настройкой языка в вебе.

Строго типизированные IOptions<TelegramOptions> с валидацией на старте; при отсутствии BotToken бот не стартует (панель работает без него).