66 lines
1.9 KiB
Python
66 lines
1.9 KiB
Python
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import hmac
|
|
import time
|
|
from typing import Any
|
|
|
|
|
|
class TelegramAuthError(ValueError):
|
|
pass
|
|
|
|
|
|
def verify_telegram_login(
|
|
payload: dict[str, Any],
|
|
bot_token: str,
|
|
*,
|
|
max_age_seconds: int = 86400,
|
|
) -> dict[str, Any]:
|
|
"""Verify Telegram Login Widget data per Telegram docs."""
|
|
received_hash = payload.get("hash")
|
|
if not received_hash or not isinstance(received_hash, str):
|
|
raise TelegramAuthError("Missing hash")
|
|
|
|
check_pairs: list[str] = []
|
|
for key in sorted(payload.keys()):
|
|
if key == "hash":
|
|
continue
|
|
value = payload[key]
|
|
if value is None:
|
|
continue
|
|
check_pairs.append(f"{key}={value}")
|
|
data_check_string = "\n".join(check_pairs)
|
|
|
|
secret_key = hashlib.sha256(bot_token.encode("utf-8")).digest()
|
|
calculated = hmac.new(
|
|
secret_key,
|
|
data_check_string.encode("utf-8"),
|
|
hashlib.sha256,
|
|
).hexdigest()
|
|
|
|
if not hmac.compare_digest(calculated, received_hash):
|
|
raise TelegramAuthError("Invalid Telegram login signature")
|
|
|
|
auth_date_raw = payload.get("auth_date")
|
|
try:
|
|
auth_date = int(auth_date_raw)
|
|
except (TypeError, ValueError) as exc:
|
|
raise TelegramAuthError("Invalid auth_date") from exc
|
|
|
|
if max_age_seconds > 0 and time.time() - auth_date > max_age_seconds:
|
|
raise TelegramAuthError("Telegram login data expired")
|
|
|
|
try:
|
|
user_id = int(payload["id"])
|
|
except (KeyError, TypeError, ValueError) as exc:
|
|
raise TelegramAuthError("Missing Telegram user id") from exc
|
|
|
|
return {
|
|
"id": user_id,
|
|
"first_name": str(payload.get("first_name") or ""),
|
|
"last_name": str(payload.get("last_name") or "") or None,
|
|
"username": str(payload.get("username") or "") or None,
|
|
"photo_url": str(payload.get("photo_url") or "") or None,
|
|
"auth_date": auth_date,
|
|
}
|