"""Utilities for validating Telegram WebApp initialization data.""" from __future__ import annotations import hashlib import hmac import json import time from typing import Any 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