"""Utilities for validating Telegram WebApp initialization data.""" from __future__ import annotations import hashlib import hmac import json import time from typing import Any, Dict from urllib.parse import parse_qsl class TelegramWebAppAuthError(Exception): """Raised when Telegram WebApp init data fails validation.""" def parse_webapp_init_data( init_data: str, bot_token: str, *, max_age_seconds: int = 86400, ) -> Dict[str, Any]: """Validate and parse Telegram WebApp init data. Args: init_data: Raw init data string provided by Telegram WebApp. bot_token: Bot token used to verify the signature. max_age_seconds: Maximum allowed age for the payload. Defaults to 24 hours. Returns: Parsed init data as a dictionary. Raises: TelegramWebAppAuthError: If validation fails. """ if not init_data: raise TelegramWebAppAuthError("Missing init data") if not bot_token: raise TelegramWebAppAuthError("Bot token is not configured") parsed_pairs = parse_qsl(init_data, strict_parsing=True, keep_blank_values=True) data: Dict[str, Any] = {key: value for key, value in parsed_pairs} received_hash = data.pop("hash", None) if not received_hash: raise TelegramWebAppAuthError("Missing init data signature") data_check_string = "\n".join( f"{key}={value}" for key, value in sorted(data.items()) ) secret_key = hmac.new( key=b"WebAppData", msg=bot_token.encode("utf-8"), digestmod=hashlib.sha256, ).digest() computed_hash = hmac.new( key=secret_key, msg=data_check_string.encode("utf-8"), digestmod=hashlib.sha256, ).hexdigest() if not hmac.compare_digest(computed_hash, received_hash): raise TelegramWebAppAuthError("Invalid init data signature") auth_date_raw = data.get("auth_date") if auth_date_raw is not None: try: auth_date = int(auth_date_raw) except (TypeError, ValueError): raise TelegramWebAppAuthError("Invalid auth_date value") from None if max_age_seconds and auth_date: current_ts = int(time.time()) if current_ts - auth_date > max_age_seconds: raise TelegramWebAppAuthError("Init data is too old") data["auth_date"] = auth_date user_payload = data.get("user") if user_payload is not None: try: data["user"] = json.loads(user_payload) except json.JSONDecodeError as error: raise TelegramWebAppAuthError("Invalid user payload") from error return data