~lord/krasovs.ky

0faac6efffbafc2a212a98411fc56dcb6179c3bd — Savely Krasovsky 14 days ago 2dea910
feat: new article about telegram
M assets/colors.scss => assets/colors.scss +9 -0
@@ 44,6 44,15 @@
    --resume-border-color: #444;
    --resume-career-place-color: #bbb;
    --resume-date-range-color: #bbb;
    --alert-error-background: #721c24;
    --alert-error-color: #f8d7da;
    --alert-error-border-color: #f5c6cb;
    --alert-success-background: #22600c;
    --alert-success-color: #dbf1d1;
    --alert-success-border-color: #dbebbe;
    --alert-info-background: #0c5460;
    --alert-info-color: #d1ecf1;
    --alert-info-border-color: #bee5eb;
    --table-border: #444;
  }
}
\ No newline at end of file

A content/blog/telegram-web-apps.md => content/blog/telegram-web-apps.md +128 -0
@@ 0,0 1,128 @@
---
title: "Telegram Web App bot: как безопасно общаться такому боту с мессенджером"
date: 2022-06-14T15:01:43+03:00
layout: post
---

С релизом нового обновления Telegram у многих возникли вопросы как правильно наладить общение `бот <-> Telegram`
и сделать это безопасно. Для начала давайте проясним какие способы активации Telegram решил предоставить пользователю:
1. Через кнопку меню, которую можно настроить у `@BotFather`.
2. Через inline-кнопку.
3. Через keyboard-кнопку.
4. Через меню вложений.

![Виды кнопок](/telegram_web_app_buttons.jpg)

Первый и второй способ предлагают нам аутентифицировать и авторизовать пользователя через специальный объект `initData`,
который можно достать с помощью JavaScript. Объект имеет следующую структуру:
```json
{
  "query_id": "str",
  "user": {
    "id": 1,
    "is_bot": false,
    "first_name": "Pavel",
    "last_name": "Durov",
    "username": "durov",
    "language_code": "ru",
    "photo_url": "https://telegram.org/durov.jpg"
  },
  "auth_date": 1655210062,
  "hash": "7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069"
}
```

Проблема в том, что официальная документация хоть и предупреждает, но не особо объясняет зачем валидировать
поле `initData` на сервере бота.

**TL;DR**

Без валидации:
> **Мэллори:** Привет Боб, я Алиса, дай мне информацию о балансе и сделай перевод на имя Мэллори.
>
> **Боб:** Пожалуйста, Алиса, всё готово.

С валидацией:
> **Мэллори:** Привет Боб, я Алиса, дай мне информацию о балансе и сделай перевод на имя Мэллори.
>
> **Боб:** Предоставьте, пожалуйста, валидную подпись Алисы.
> 
> _Мэллори разводит руками_

То есть нужно это затем, чтобы произвести безопасную аутентификацию (проверку подлинности запроса) и авторизацию
(понять, что к боту пришла именно Алиса, а не Мэллори).

Без этой валидации бот сможет предоставлять критически важную информацию (например, вы делаете онлайн-банкинг в боте)
просто по ID пользователя Telegram.

Важно также понимать, что валидация должна происходить **исключительно** на серверной части.
Валидация на клиентской части мало того, что бессмысленна, так ещё и скомпрометирует токен вашего бота.

Валидация, к слову, не совсем тривиальная. Разработчики Telegram, как обычно, не поленились и вместо проверенного
стандарта JSON Web Token (JWT), реализовали свой собственный велосипед, да ещё и на базе обычного HMAC-SHA256
(то есть HS256 будь у нас JWT-токен). В результате `initData` представляет собой URL-encoded
строку query-параметров. Для корректной валидации которой требуется следующая цепочка шагов:

1. Декодируем строку, используя URL-encoding (важно, иначе значение с ключом `user` останется не декодированным).
2. Полученные пары ключ-значения сортируем в алфавитном порядке.
3. Исключаем ключ `hash`
4. Из полученных пар составляем тело вида: `auth_date=<auth_date>\nquery_id=<query_id>\nuser=<user>`. Важно сохранить значение с ключом `user` в чистом JSON.
5. Берем хэш от токена вашего бота с помощью алгоритма HMAC-SHA256 с ключом `WebAppData`.
6. Берем хэш от полученного в шаге 4 тела с помощью того же алгоритма, а в качестве ключа используем хэш, полученный ранее в виде последовательности **байтов** (а не hex-репрезентации!).
7. Преобразуем полученный хэш в hex-строку и сравниваем со значением ключа `hash`.

По аналогии с JWT, если валидация прошла успешно, пользователя можно считать аутентифицированным
и переходить к авторизации с помощью предоставленного payload (в нашем случае это `id` в объекте `user`).

Говоря о `query_id`, который мы получаем в `initData`. Используя это поле, мы можем создать в чате сообщение с ботом
от имени пользователя с бейджем `via @your_bot`. Для этого потребуется вызвать метод [answerWebAppQuery](https://core.telegram.org/bots/api#answerwebappquery).

![Вид такого сообщения](/telegram_web_app_via.png)

<div class="alert alert-success">
+ может использоваться для приложений любой сложности<br/>
+ <code>initData</code> по факту является полноценным stateless-токеном по типу JWT<br/>
+ позволяет создавать сообщения от имени пользователя в чате с ботом с бейджем <code>via @your_bot</code><br/>
</div>
<div class="alert alert-error">
- требуется собственный бэкенд для веб-части для валидации <code>initData</code> и работы с пользователем
</div>

Вернёмся к способам активации. С первым и вторым способом всё понятно: вы получаете от Telegram подобие готового токена
и поэтому реализация собственной аутентификации и авторизации не требуется, требуется только валидация.

Но с третьим способом ситуация с одной стороны проще, с другой сложней. Дело в том, что `initData` не приходит
и наладить общение с серверной частью не выйдет. Ведь вы не будете знать кто к вам пришёл.

Однако при использовании этого способа появляется возможность использовать метод `Telegram.WebApp.sendData()`,
который позволяет отправить сообщение боту напрямую, а тот предоставит его вам через long-polling или вебхуки.
Стоит учесть, что после успешного выполнения веб-окно автоматически закроется, а бот отрапортует сервисным сообщением
`Вы успешно передали данные боту кнопкой «Test button».`

Поэтому Telegram позиционирует этот способ как удобный способ сделать гибкую веб-форму ввода с полями
типа `date picker`. Вернуть значения формы можно с помощью метода `Telegram.WebApp.sendData()`.

Нужно понимать, что в JS-файле этот метод является лишь прослойкой,
само значение, переданное в `sendData()` отправляются далее через
[MTProto-метод](https://github.com/tdlib/td/blob/047246f366d94b55ea7b6b93b3a4baa9a3380154/td/generate/scheme/td_api.tl#L4968)
`sendWebAppData`. Методы MTProto невозможно использовать без авторизации в Telegram, поэтому тут мессенджер берет
безопасность полностью на себя.

В этом заключается плюс этого метода.

<div class="alert alert-success">
+ удобно для заполнения сравнительно простых форм ввода<br/>
+ наличие собственного бэкенда для Web-части не требуется<br/>
</div>
<div class="alert alert-error">
- <code>initData</code> не приходит, возможность авторизовать пользователя на своём бэкенде (даже если он есть) отсутствует<br/>
- отправить информацию боту можно только 1 раз
</div>

Есть также четвертый способ, который технически не отличается от первого и второго
(только дополнительными полями в `initData`), но в этом случае бот добавляется
в меню вложений.

К сожалению эта возможность пока что доступна только для тех, кто участвует в рекламной платформе Telegram
и, следовательно, внёс залог 2 миллиона евро. Поэтому пока что говорить тут особо не о чем.
Из известных мне публичных ботов такую интеграцию использует [@wallet](https://t.me/wallet).
\ No newline at end of file

A static/telegram_web_app_buttons.jpg => static/telegram_web_app_buttons.jpg +0 -0
A static/telegram_web_app_via.png => static/telegram_web_app_via.png +0 -0