api / auth.py
jamelloverz-sketch
feat: unified FastAPI v3 - merge all APIs into main org space
8c4dbdb
Raw
History Blame
2.71 kB
import hmac
import hashlib
import json
import os
import time
import urllib.parse
from fastapi import Request, HTTPException
from config import TELEGRAM_BOT_TOKEN
DEV_MODE = os.environ.get("DEV_MODE", "").lower() in ("true", "1", "yes")
def verify_init_data(init_data: str) -> dict | None:
if DEV_MODE and init_data.startswith("dev_"):
parts = init_data.split("_")
return {
"id": int(parts[1]) if len(parts) > 1 and parts[1].isdigit() else 5703679073,
"first_name": parts[2] if len(parts) > 2 else "Dev",
"last_name": "",
"username": parts[3] if len(parts) > 3 else "devuser",
"language_code": "en",
"is_premium": False,
}
try:
parsed = dict(urllib.parse.parse_qsl(init_data))
except Exception:
return None
hash_val = parsed.pop("hash", None)
if not hash_val:
return None
auth_date = parsed.get("auth_date")
if not auth_date:
return None
try:
if time.time() - int(auth_date) > 86400:
return None
except (ValueError, TypeError):
return None
sorted_items = sorted(parsed.items())
check_str = "\n".join(f"{k}={v}" for k, v in sorted_items)
secret_key = hmac.new(b"WebAppData", TELEGRAM_BOT_TOKEN.encode(), hashlib.sha256).digest()
computed_hash = hmac.new(secret_key, check_str.encode(), hashlib.sha256).hexdigest()
if not hmac.compare_digest(computed_hash, hash_val):
return None
try:
user_info = json.loads(parsed.get("user", "{}"))
except (json.JSONDecodeError, TypeError):
user_info = {}
return {
"id": user_info.get("id"),
"first_name": user_info.get("first_name", ""),
"last_name": user_info.get("last_name", ""),
"username": user_info.get("username", ""),
"language_code": user_info.get("language_code", "en"),
"is_premium": user_info.get("is_premium", False),
"photo_url": user_info.get("photo_url", ""),
}
async def get_current_user(request: Request) -> dict:
auth_header = request.headers.get("Authorization", "")
if not auth_header.startswith("tma "):
raise HTTPException(status_code=401, detail="Missing or invalid Authorization header")
user_data = verify_init_data(auth_header[4:])
if not user_data or not user_data.get("id"):
raise HTTPException(status_code=401, detail="Invalid init data")
return user_data
async def optional_current_user(request: Request) -> dict | None:
auth_header = request.headers.get("Authorization", "")
if not auth_header.startswith("tma "):
return None
return verify_init_data(auth_header[4:])