mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-02-27 14:51:19 +00:00
- Add pyproject.toml with uv and ruff configuration - Pin Python version to 3.13 via .python-version - Add Makefile commands: lint, format, fix - Apply ruff formatting to entire codebase - Remove unused imports (base64 in yookassa/simple_subscription) - Update .gitignore for new config files
128 lines
4.2 KiB
Python
128 lines
4.2 KiB
Python
"""Telegram authentication validation for cabinet."""
|
|
|
|
import hashlib
|
|
import hmac
|
|
import json
|
|
from datetime import datetime
|
|
from typing import Any
|
|
from urllib.parse import parse_qsl, unquote
|
|
|
|
from app.config import settings
|
|
|
|
|
|
def validate_telegram_login_widget(data: dict[str, Any], max_age_seconds: int = 86400) -> bool:
|
|
"""
|
|
Validate Telegram Login Widget data.
|
|
|
|
https://core.telegram.org/widgets/login#checking-authorization
|
|
|
|
Args:
|
|
data: Dictionary with Telegram login data (id, first_name, auth_date, hash, etc.)
|
|
max_age_seconds: Maximum allowed age of auth_date (default 24 hours)
|
|
|
|
Returns:
|
|
True if data is valid, False otherwise
|
|
"""
|
|
auth_data = data.copy()
|
|
check_hash = auth_data.pop('hash', None)
|
|
|
|
if not check_hash:
|
|
return False
|
|
|
|
# Check auth_date is not too old
|
|
auth_date = auth_data.get('auth_date')
|
|
if auth_date:
|
|
try:
|
|
# Use UTC timestamp to avoid timezone issues
|
|
auth_time = datetime.utcfromtimestamp(int(auth_date))
|
|
age = (datetime.utcnow() - auth_time).total_seconds()
|
|
if age > max_age_seconds:
|
|
return False
|
|
except (ValueError, TypeError, OSError):
|
|
return False
|
|
|
|
# Build data-check-string (sorted key=value pairs, newline-separated)
|
|
data_check_arr = [f'{k}={v}' for k, v in sorted(auth_data.items()) if v is not None]
|
|
data_check_string = '\n'.join(data_check_arr)
|
|
|
|
# Create secret key from bot token using SHA256
|
|
bot_token = settings.BOT_TOKEN
|
|
secret_key = hashlib.sha256(bot_token.encode()).digest()
|
|
|
|
# Calculate expected hash
|
|
calculated_hash = hmac.new(secret_key, data_check_string.encode(), hashlib.sha256).hexdigest()
|
|
|
|
return hmac.compare_digest(calculated_hash, check_hash)
|
|
|
|
|
|
def validate_telegram_init_data(init_data: str, max_age_seconds: int = 86400) -> dict[str, Any] | None:
|
|
"""
|
|
Validate Telegram WebApp initData.
|
|
|
|
https://core.telegram.org/bots/webapps#validating-data-received-via-the-mini-app
|
|
|
|
Args:
|
|
init_data: Raw initData string from Telegram WebApp
|
|
max_age_seconds: Maximum allowed age of auth_date (default 24 hours)
|
|
|
|
Returns:
|
|
Parsed user data dict if valid, None otherwise
|
|
"""
|
|
try:
|
|
# Parse the init_data string
|
|
parsed = dict(parse_qsl(init_data, keep_blank_values=True))
|
|
|
|
received_hash = parsed.pop('hash', None)
|
|
if not received_hash:
|
|
return None
|
|
|
|
# Check auth_date is not too old
|
|
auth_date = parsed.get('auth_date')
|
|
if auth_date:
|
|
try:
|
|
# Use UTC timestamp to avoid timezone issues
|
|
auth_time = datetime.utcfromtimestamp(int(auth_date))
|
|
age = (datetime.utcnow() - auth_time).total_seconds()
|
|
if age > max_age_seconds:
|
|
return None
|
|
except (ValueError, TypeError, OSError):
|
|
return None
|
|
|
|
# Build data-check-string
|
|
data_check_arr = [f'{k}={v}' for k, v in sorted(parsed.items())]
|
|
data_check_string = '\n'.join(data_check_arr)
|
|
|
|
# Create secret key: HMAC_SHA256(bot_token, "WebAppData")
|
|
bot_token = settings.BOT_TOKEN
|
|
secret_key = hmac.new(b'WebAppData', bot_token.encode(), hashlib.sha256).digest()
|
|
|
|
# Calculate expected hash
|
|
calculated_hash = hmac.new(secret_key, data_check_string.encode(), hashlib.sha256).hexdigest()
|
|
|
|
if not hmac.compare_digest(calculated_hash, received_hash):
|
|
return None
|
|
|
|
# Parse user data from the validated data
|
|
user_data_str = parsed.get('user')
|
|
if user_data_str:
|
|
user_data = json.loads(unquote(user_data_str))
|
|
return user_data
|
|
|
|
return parsed
|
|
|
|
except (ValueError, TypeError, json.JSONDecodeError):
|
|
return None
|
|
|
|
|
|
def extract_telegram_user_from_init_data(init_data: str) -> dict[str, Any] | None:
|
|
"""
|
|
Extract and validate user info from Telegram WebApp initData.
|
|
|
|
Args:
|
|
init_data: Raw initData string from Telegram WebApp
|
|
|
|
Returns:
|
|
User data dict with id, first_name, last_name, username, etc. or None if invalid
|
|
"""
|
|
return validate_telegram_init_data(init_data)
|