82 lines
2.7 KiB
Python
82 lines
2.7 KiB
Python
"""
|
||
Telegram Mini App — валидация initData.
|
||
|
||
Telegram передаёт подписанные данные при открытии WebApp.
|
||
Мы проверяем HMAC-SHA256 подпись через BOT_TOKEN,
|
||
чтобы убедиться что запрос пришёл от реального пользователя.
|
||
|
||
Документация: https://core.telegram.org/bots/webapps#validating-data
|
||
"""
|
||
|
||
import hashlib
|
||
import hmac
|
||
import json
|
||
import os
|
||
import time
|
||
from urllib.parse import parse_qs
|
||
|
||
BOT_TOKEN = os.getenv("BOT_TOKEN", "")
|
||
|
||
|
||
def validate_init_data(init_data: str, max_age_seconds: int = 86400) -> dict | None:
|
||
"""
|
||
Валидирует initData из Telegram WebApp.
|
||
|
||
Возвращает dict с данными пользователя при успехе, None при ошибке.
|
||
"""
|
||
if not init_data or not BOT_TOKEN:
|
||
return None
|
||
|
||
try:
|
||
parsed = parse_qs(init_data, keep_blank_values=True)
|
||
|
||
# Извлекаем hash
|
||
received_hash = parsed.get("hash", [None])[0]
|
||
if not received_hash:
|
||
return None
|
||
|
||
# Собираем data-check-string (все поля кроме hash, отсортированные)
|
||
data_pairs = []
|
||
for key, values in parsed.items():
|
||
if key == "hash":
|
||
continue
|
||
data_pairs.append(f"{key}={values[0]}")
|
||
data_pairs.sort()
|
||
data_check_string = "\n".join(data_pairs)
|
||
|
||
# Вычисляем секретный ключ
|
||
secret_key = hmac.new(
|
||
b"WebAppData", BOT_TOKEN.encode("utf-8"), hashlib.sha256
|
||
).digest()
|
||
|
||
# Вычисляем HMAC
|
||
computed_hash = hmac.new(
|
||
secret_key, data_check_string.encode("utf-8"), hashlib.sha256
|
||
).hexdigest()
|
||
|
||
if not hmac.compare_digest(computed_hash, received_hash):
|
||
return None
|
||
|
||
# Проверяем auth_date (не старше max_age_seconds)
|
||
auth_date_str = parsed.get("auth_date", [None])[0]
|
||
if auth_date_str:
|
||
auth_date = int(auth_date_str)
|
||
if time.time() - auth_date > max_age_seconds:
|
||
return None
|
||
|
||
# Парсим user
|
||
user_str = parsed.get("user", [None])[0]
|
||
if not user_str:
|
||
return None
|
||
|
||
user_data = json.loads(user_str)
|
||
return {
|
||
"user_id": user_data.get("id"),
|
||
"first_name": user_data.get("first_name", ""),
|
||
"last_name": user_data.get("last_name", ""),
|
||
"username": user_data.get("username", ""),
|
||
"language_code": user_data.get("language_code", "ru"),
|
||
}
|
||
|
||
except Exception:
|
||
return None
|