|
""" |
|
Check Telegram WebAppData and LoginWidgetData in Python with FastAPI. |
|
|
|
Source: https://gist.github.com/dantetemplar/68e09e3674bf6ce72e23b89b2091d159 |
|
License: MIT License |
|
Author: Ruslan Belkov |
|
""" |
|
|
|
import hashlib |
|
import hmac |
|
import json |
|
import time |
|
|
|
from fastapi import FastAPI, HTTPException, Request |
|
from pydantic import BaseModel |
|
from starlette.responses import HTMLResponse |
|
|
|
BOT_TOKEN = "YOUR_BOT_TOKEN" # TODO: Replace with your bot token |
|
BOT_USERNAME = "YOUR_BOT_USERNAME" # TODO: Replace with your bot username |
|
FOR_WEBAPP = False # TODO: Set to True if you are using WebAppData, False if you are using LoginWidget |
|
|
|
app = FastAPI() |
|
|
|
|
|
class VerificationResult(BaseModel): |
|
success: bool |
|
telegram_user: dict | None = None |
|
|
|
|
|
def telegram_check(params: dict) -> VerificationResult: |
|
""" |
|
Verify telegram data |
|
|
|
https://core.telegram.org/widgets/login#checking-authorization |
|
""" |
|
if "hash" not in params or "auth_date" not in params: |
|
return VerificationResult(success=False) |
|
|
|
# use params = {k: v[0] for k, v in parse_qs(quoted_string).items()} if you have a string |
|
received_hash = params["hash"] |
|
auth_date = int(params["auth_date"]) |
|
to_hash = "\n".join([f"{k}={v}" for k, v in sorted(params.items()) if k != "hash"]) |
|
encoded_telegram_data = to_hash.encode("utf-8").decode("unicode-escape").encode("ISO-8859-1") |
|
|
|
token = BOT_TOKEN |
|
|
|
if FOR_WEBAPP: # Use hmac(key="WebAppData", msg=token, digestmode=sha256) as secret key |
|
secret_key = hmac.new(b"WebAppData", token.encode(), hashlib.sha256).digest() |
|
else: # Use just sha256(token) as secret key |
|
secret_key = hashlib.sha256(token.encode()).digest() |
|
evaluated_hash = hmac.new(secret_key, encoded_telegram_data, hashlib.sha256).hexdigest() |
|
|
|
if time.time() - auth_date > 86400: |
|
return VerificationResult(success=False) # Data is older than 24 hours |
|
|
|
success = hmac.compare_digest(evaluated_hash, received_hash) |
|
|
|
if success: |
|
if FOR_WEBAPP: |
|
user = json.loads(params["user"]) |
|
else: |
|
user = {k: v for k, v in params.items() if k not in ["hash", "auth_date"]} |
|
return VerificationResult(success=True, telegram_user=user) |
|
else: |
|
return VerificationResult(success=False) # Data is not from Telegram |
|
|
|
|
|
@app.get("/telegram-widget.html", response_class=HTMLResponse) |
|
async def telegram_widget(request: Request): |
|
callback_url = request.url_for("telegram_callback") |
|
|
|
content = f""" |
|
<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<title>Telegram Widget</title> |
|
</head> |
|
<body> |
|
<script async src="https://telegram.org/js/telegram-widget.js?22" data-telegram-login="{BOT_USERNAME}" data-size="medium" data-onauth="onTelegramAuth(user)" data-request-access="write"></script> |
|
<script> |
|
function onTelegramAuth(user) {{ |
|
console.log(user); |
|
fetch("{callback_url}" + "?" + new URLSearchParams(user), {{"method": "POST"}}) |
|
.then(response => response.json()) |
|
.then(data => {{ |
|
if (data.success) {{ |
|
alert("Telegram data verified successfully"); |
|
}} else {{ |
|
alert("Telegram data verification failed"); |
|
}} |
|
}}) |
|
.catch(error => {{ |
|
console.error("Error:", error); |
|
}}); |
|
}} |
|
</script> |
|
</body> |
|
</html> |
|
""" |
|
|
|
return HTMLResponse(content) |
|
|
|
|
|
@app.post("/telegram-callback/") |
|
async def telegram_callback(request: Request) -> VerificationResult: |
|
result = telegram_check(request.query_params._dict) |
|
if not result.success: |
|
raise HTTPException(status_code=403, detail="Telegram data verification failed") |
|
return result |