import hashlib
import hmac
import time
from urllib.parse import parse_qs
from ..exception import WebAppDataInvalid, WebAppDataOutdated, WebAppDataMismatch
_webapp_secret_key = b"WebAppData"
[docs]
def create_webapp_secret_key(bot_token: str) -> bytes:
r"""Create a secret key for Web App data validation
\Parameters:
bot_token (``str``):
Bot token
"""
return hmac.new(
key=_webapp_secret_key,
msg=bot_token.encode("utf-8"),
digestmod=hashlib.sha256,
).digest()
[docs]
def parse_webapp_data(
secret_key: bytes, init_data: str, max_data_age: int = 60
) -> dict:
r"""Parse and validate init data received from Web App
\Parameters:
secret_key (``bytes``):
Secret key for Web App data validation; can be created using :func:`pytdbot.utils.create_webapp_secret_key`
init_data (``str``):
Init data received from Web App
max_data_age (``int``, *optional*):
Maximum age of init data in seconds. Default is ``60`` seconds
Returns:
``dict``: Parsed data
Raises:
:class:`pytdbot.exception.WebAppDataInvalid`
:class:`pytdbot.exception.WebAppDataOutdated`
:class:`pytdbot.exception.WebAppDataMismatch`
"""
assert isinstance(secret_key, bytes), "secret_key must be bytes"
assert isinstance(init_data, str), "init_data must be a string"
assert isinstance(max_data_age, int), "max_data_age must be an int"
# In Python 3.8.7 or earlier, parse_qs treats ';' as query separator in addition to '&'
# Which may cause issues with the hash validation
data = parse_qs(init_data)
data = {k: v[0] for k, v in data.items()}
if "hash" not in data or "auth_date" not in data:
raise WebAppDataInvalid("Missing hash or auth_date")
if int(data["auth_date"]) < int(time.time() - max_data_age):
raise WebAppDataOutdated
received_hash = data.pop("hash")
sorted_keys = sorted(data.keys())
data_check_string = "\n".join([f"{key}={data[key]}" for key in sorted_keys])
expected_hash = hmac.new(
key=secret_key, msg=data_check_string.encode("utf-8"), digestmod=hashlib.sha256
).hexdigest()
if not hmac.compare_digest(expected_hash, received_hash):
raise WebAppDataMismatch("Hash mismatch")
return data