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

152 lines
13 KiB
Markdown
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.
# 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](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-подпись данных
ботом, верификация на бэке). Оставлено как опция; основной путь — подтверждение в боте.
## Конфигурация
```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<TelegramOptions>` с валидацией на старте; при отсутствии
`BotToken` бот не стартует (панель работает без него).