This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
from functools import lru_cache
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
model_config = SettingsConfigDict(env_file=".env", extra="ignore")
|
||||
|
||||
bot_token: str = ""
|
||||
webhook_url: str = ""
|
||||
webhook_path: str = "/webhook/telegram"
|
||||
webhook_secret: str = ""
|
||||
|
||||
database_url: str = "postgresql+asyncpg://postgres:postgres@postgres:5432/tglotterybot"
|
||||
|
||||
mini_app_url: str = ""
|
||||
|
||||
avatar_cache_ttl: int = 3600
|
||||
|
||||
debug: bool = False
|
||||
|
||||
|
||||
@lru_cache()
|
||||
def get_settings() -> Settings:
|
||||
return Settings()
|
||||
|
||||
|
||||
settings = get_settings()
|
||||
@@ -0,0 +1,10 @@
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
|
||||
from app.core.config import settings
|
||||
|
||||
engine = create_async_engine(settings.database_url, echo=settings.debug)
|
||||
AsyncSessionLocal = async_sessionmaker(engine, expire_on_commit=False)
|
||||
|
||||
|
||||
async def get_db():
|
||||
async with AsyncSessionLocal() as session:
|
||||
yield session
|
||||
@@ -0,0 +1,59 @@
|
||||
import hmac
|
||||
import hashlib
|
||||
import time
|
||||
from urllib.parse import parse_qsl
|
||||
|
||||
from app.core.config import settings
|
||||
|
||||
# Reject initData older than this many seconds (24 hours)
|
||||
_MAX_AGE_SECONDS = 86_400
|
||||
|
||||
|
||||
def validate_init_data(init_data: str) -> dict | None:
|
||||
"""Validate Telegram WebApp initData using HMAC-SHA256.
|
||||
|
||||
Also rejects payloads whose auth_date is older than _MAX_AGE_SECONDS to
|
||||
prevent indefinite replay of leaked initData strings.
|
||||
"""
|
||||
try:
|
||||
# parse_qsl already URL-decodes each value. Decoding the whole query
|
||||
# string first can turn escaped separators inside JSON values into real
|
||||
# separators and make Telegram's HMAC check fail.
|
||||
parsed = dict(parse_qsl(init_data, keep_blank_values=True))
|
||||
hash_value = parsed.pop("hash", "")
|
||||
if not hash_value:
|
||||
return None
|
||||
|
||||
# Validate auth_date freshness before doing any crypto work
|
||||
auth_date_str = parsed.get("auth_date")
|
||||
if not auth_date_str:
|
||||
return None
|
||||
try:
|
||||
age = time.time() - int(auth_date_str)
|
||||
if age > _MAX_AGE_SECONDS:
|
||||
return None
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
data_check_string = "\n".join(
|
||||
f"{k}={v}" for k, v in sorted(parsed.items())
|
||||
)
|
||||
|
||||
secret_key = hmac.new(
|
||||
b"WebAppData",
|
||||
settings.bot_token.encode(),
|
||||
hashlib.sha256,
|
||||
).digest()
|
||||
|
||||
expected = hmac.new(
|
||||
secret_key,
|
||||
data_check_string.encode(),
|
||||
hashlib.sha256,
|
||||
).hexdigest()
|
||||
|
||||
if not hmac.compare_digest(expected, hash_value):
|
||||
return None
|
||||
|
||||
return parsed
|
||||
except Exception:
|
||||
return None
|
||||
Reference in New Issue
Block a user