13 KiB
Telegram Bot
Telegram-бот — второй канал доставки (presentation-адаптер) поверх той же Application-логики,
что и REST API. Он не содержит бизнес-правил: команды бота вызывают те же CQRS-команды/запросы
(ICommand/IQuery), что и веб. Бизнес-инварианты живут в домене, а не в обработчиках бота.
Возможности
- Ссылка на сайт — кнопка/команда, открывающая веб-панель (при желании — с одноразовым deep-link авто-входом для уже привязанного пользователя).
- Мои конфиги — список VPN-конфигов пользователя (протокол, локация, трафик, срок, статус), ссылка-подписка и QR по каждому. Доступно только привязанному аккаунту.
- Авторизация через Telegram (passwordless) — вход на сайт без пароля: подтверждение входа в боте. Требует предварительной привязки Telegram к аккаунту.
- Админ: обработка запросов активации — админ (по Telegram id из env) получает уведомление о запросе активации с комментарием заявителя и жмёт «Активировать / Отклонить» прямо в боте.
- DM-уведомления пользователю — если Telegram привязан, бот шлёт личные уведомления о ключевых событиях: «аккаунт активирован», «конфиг отозван админом», «вы заблокированы».
- Восстановление пароля — если пароль забыт, привязанный пользователь через бота получает одноразовую ссылку на страницу задания нового пароля (или входит 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.
Флоу 1 — Привязка Telegram к аккаунту
Предусловие: пользователь уже вошёл на сайте (изначально аккаунт создаётся с username+пароль).
- На сайте «Привязать Telegram» →
POST /api/auth/telegram/link-token→{ deepLink }видаhttps://t.me/<bot>?start=link_<token>(+ QR). Токен короткоживущий, одноразовый. - Пользователь открывает бота по ссылке →
/start link_<token>. - Бот берёт
from.id(Telegram user id), валидирует токен (LinkTelegramCommand), проставляетTelegramUserId/TelegramUsername/TelegramLinkedAt, гасит токен. - Бот подтверждает: «Аккаунт привязан». Сайт узнаёт об успехе (поллинг статуса или SignalR).
Инварианты: один TelegramUserId ↔ один аккаунт; повторная привязка требует отвязки; токен
нельзя переиспользовать и он истекает.
Флоу 2 — Passwordless-вход через бота
Предусловие: Telegram уже привязан к аккаунту.
- На сайте «Войти через Telegram» →
POST /api/auth/telegram/login-request→{ requestId, deepLink, qr, expiresAt }. Сайт начинает ждать результат (поллингGET /api/auth/telegram/login-request/{requestId}или событие SignalR). - Пользователь открывает
https://t.me/<bot>?start=login_<nonce>→ бот поfrom.idнаходит привязанный аккаунт и показывает inline-кнопки «Подтвердить вход / Отклонить» (с деталями: время, IP/устройство инициатора — для защиты от фишинга). - Подтверждение (
ApproveTelegramLoginCommand) → запрос переходит вApproved, привязывается кuserId. - Сайт (по поллингу/SignalR) получает результат: бэкенд выпускает стандартные JWT —
access в теле ответа, refresh в httpOnly cookie. Запрос помечается
Consumed.
Если Telegram не привязан — passwordless-вход невозможен (бот предлагает сперва привязать аккаунт). Регистрация целиком через Telegram — вне MVP (см. backlog).
Флоу 3 — Просмотр конфигов в боте
- Привязанный пользователь:
/configsили кнопка «Мои конфиги». - Бот вызывает
GetMyConfigsQuery(тот же, что и веб) от имениAppUser, найденного поTelegramUserId. - Ответ — список с трафиком/сроком/статусом; по каждому конфигу — inline-кнопки «Ссылка», «QR». QR отдаётся как изображение (генерация на сервере).
Флоу 4 — Обработка активации админом в боте
- Пользователь отправляет запрос активации (сайт:
POST /api/activation/request { comment }); доменное событиеActivationRequested. - Бот шлёт сообщение каждому админу (Telegram id из
AdminSeed__TelegramUserIds) с username/комментарием заявителя и inline-кнопками «✅ Активировать / ❌ Отклонить». - Нажатие →
ApproveActivationCommand/RejectActivationCommand(те же, что на сайте) → пользователь активируется, ему уходит realtime-пушuserActivated, админам обновляется сообщение (решение зафиксировано). - Действие доступно только Telegram id из списка админов; проверка — на стороне бота перед вызовом команды.
Команды и клавиатуры
| Команда / кнопка | Действие | Требует привязки |
|---|---|---|
/start |
Приветствие + меню (Открыть сайт / Мои конфиги / Войти) | нет |
/start link_<t> |
Привязка аккаунта по токену | нет |
/start login_<n> |
Подтверждение 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-подпись данных ботом, верификация на бэке). Оставлено как опция; основной путь — подтверждение в боте.
Конфигурация
"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 бот не стартует (панель работает без него).