Как стать автором
Обновить

Часть 2.5. TMA на KMP. Аутентификации пользователя с DRF

Уровень сложностиПростой
Время на прочтение4 мин
Количество просмотров594

Эта короткая статья является дополнением ко второй, но можно прочить независимо, если требуется только реализация серверной части аутентификации.

Навигация по циклу статей:

Часть 1. Пишем веб-приложение кликер на Kotlin
Часть 2. Пишем кликер для Telegram на Kotlin
Часть 2.5. Аутентификация пользователя с DRF - текущая статья
Часть 3. Добавляем оплату через Telegram Mini Apps на Kotlin

Раскрытые темы в цикле

  • Web приложение на Kotlin – часть 1

  • Интеграция приложения с Telegram Mini Apps – часть 2

  • Работа с элементами интерфейса TMA приложения. Тема, MainButtonBackButton – часть 2

  • Поделиться ссылкой на приложение через Telegram. Передача данных через ссылку – часть 2

  • Аутентификации через TMA приложение – часть 2 и 2.5

  • Telegram Payments API– часть 3

Техническое задание. Кратко

  • Аутентификация через TMA приложение

  • Обработка данных, используемых для работы реферальной системы

Аутентификация с Django Rest Framework

Поскольку будет использоваться собственный заголовок tma-data, где лежат сырые initData от TMA API, и собственный алгоритм валидации данных, то будем использовать BaseAuthentication

Сама функция валидации tma-data разделяет отправленные данные, удаляет из них hash, сортирует и склеивает обратно в строку с разделителем начала новой строки. Дальше с использование hmac сравнивает с hash. Эта проверка выполняется на основе документации Telegram.

from urllib.parse import unquote_plus
import hashlib
import hmac

def validate_data(data: str, secret_key):
    decoded = unquote_plus(data).split('&')
    filtered = filter(lambda a: not a.startswith('hash='), decoded)
    data_hash = ''.join(list(filter(lambda a: a.startswith('hash='), decoded))\[0\]\[5:\])
    sorted_data = sorted(filtered)
    data_check = '\\n'.join(sorted_data)
    return hmac.new(secret_key, data_check.encode(), hashlib.sha256).hexdigest() == data_hash

secret_key – не изменяется и генерируется на основе токена бота и статического ключа – строки “WebAppData”

secret_key = hmac.new("WebAppData".encode(), settings.TELEGRAM_API_TOKEN.encode(), hashlib.sha256).digest()

Далее создаём класс, к примеру TMAAuthentication, наследника BaseAuthentication, и выполняем проверку отправленных данных

class TMAAuthentication(BaseAuthentication):
    secret_key = hmac.new("WebAppData".encode(), settings.TELEGRAM_API_TOKEN.encode(), hashlib.sha256).digest()

    def authenticate(self, request: Request):
        tma_data = request.headers.get('tma-data', None)
        if tma_data is None or len(tma_data) == 0 or not validate_data(tma_data, secret_key=self.secret_key):
            return None

        data = parse_qs(tma_data)
        user_data = get_user_data(data)
        if user_data is None:
            return None
        user, is_new = get_user_or_create(user_data)
        user.is_authenticated = True
        return user, tma_data

Для получения или создания (при первом входе) создаём в базе даных пользователя. Здесь уже бекендер решает, как лучше сохранять пользователей, статья про то, как валидировать данные с TMA клиента.

def get_user_or_create(user):
    return TelegramUser.objects.get_or_create(
        pk=user['id'],
        defaults={
            "username": user['username'],
            "first_name": user['first_name'] if user['first_name'] is not None else '',
            "last_name": user['last_name'] if user['last_name'] is not None else ''
        },
    )

Теперь подключим к нашим APIView созданный TMAAuthentication.

def get_user_or_create(user):
    return TelegramUser.objects.get_or_create(
        pk=user['id'],
        defaults={
            "username": user['username'],
            "first_name": user['first_name'] if user['first_name'] is not None else '',
            "last_name": user['last_name'] if user['last_name'] is not None else ''
        },
    )

Start param. Direct link. Реферальная ссылка

Теперь добавим обработку реферальных ссылок. Разберём только то, как можно получить эти данные. Где и в какой момент их получать решать вам.

Структура ссылки проста:   https://t.me/botusername/appname?startapp=command. Как получить возможнось перехода по таким ссылкам показывалось во второй части цикла

startapp – это и есть параметр, который мы можем передать через ссылку и обработать в клиенте, поскольку он становится частью WebAppInitData с ключём start_param.

В нашем приложении мы будем создавать ссылку, где startapp=ref_{user_id}. А извлечение её на стороне сервера будет выглядеть

def get_user_or_create(user):
    return TelegramUser.objects.get_or_create(
        pk=user['id'],
        defaults={
            "username": user['username'],
            "first_name": user['first_name'] if user['first_name'] is not None else '',
            "last_name": user['last_name'] if user['last_name'] is not None else ''
        },
    )

Из функции мы возвращаем числовое значение user_id, с которым мы можем работать далее.

К примеру, добавить человека в список друзей, если его ещё там нет

def create_friend_rel(user: TelegramUser, request: Request):
    ref = get_ref_user_id(request)
    if ref is None or ref == user.id:
        return

    first_user = TelegramUser.objects.get(id=ref)
    rel_1 = get_or_none(FriendRelationship, first=first_user, second=user)
    rel_2 = get_or_none(FriendRelationship, first=user, second=first_user)
    if rel_1 is not None or rel_2 is not None:
        return
    rel = FriendRelationship(first=first_user, second=user, is_invitation=user.is_new)
    rel.save()

Для простоты в нашем кликере местом вызова будет авторизация пользователя. Так делать на самом не стоит, лишняя операция на авторизацию.

class TMAAuthentication(BaseAuthentication):
    secret_key = hmac.new("WebAppData".encode(), settings.TELEGRAM_API_TOKEN.encode(), hashlib.sha256).digest()

    def authenticate(self, request: Request):
        # ...
        user, is_new = get_user_or_create(user_data)
        user.is_authenticated = True
        user.is_new = is_new
        create_friend_rel(user, request)
        return user, tma_data

Итоги

Теперь кликер может авторизовываться, используя свой аккаунт в Telegram и передавать различные данные по через ссылку входа в приложение, а именно реферальный код. Остальную часть реализации работы приложения показывать в статье смысла показывать нет, много других статей с информацией о работе с Django и DRF, наша цель же показать как можно обрабатывать данные конкретно из TMA приложения. И, что интересно, мы всё ещё ни разу не запустили нашего бота

Теги:
Хабы:
Всего голосов 1: ↑1 и ↓0+3
Комментарии1

Публикации

Истории

Работа

Python разработчик
195 вакансий

Ближайшие события

27 августа – 7 октября
Премия digital-кейсов «Проксима»
МоскваОнлайн
28 сентября – 5 октября
О! Хакатон
Онлайн
3 – 18 октября
Kokoc Hackathon 2024
Онлайн
10 – 11 октября
HR IT & Team Lead конференция «Битва за IT-таланты»
МоскваОнлайн
25 октября
Конференция по росту продуктов EGC’24
МоскваОнлайн
7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн