"""Telegram Login Widget провайдер. Проверяет подпись данных виджета (HMAC-SHA256 ключом SHA256(BOT_TOKEN)) и свежесть auth_date. Нужны TELEGRAM_BOT_TOKEN (+ TELEGRAM_BOT_USERNAME для виджета на фронте). Доступен во всех окружениях — наряду со входом по логину и паролю. """ from __future__ import annotations import hashlib import hmac import time from typing import Any from app.auth.provider import AuthProvider, ExternalIdentity from app.core.config import settings from app.core.errors import AuthError _MAX_AUTH_AGE_SECONDS = 86400 # сутки class TelegramProvider(AuthProvider): name = "telegram" def authenticate(self, payload: dict[str, Any]) -> ExternalIdentity: token = settings.telegram_bot_token if not token: raise AuthError("Telegram-вход не настроен (нет TELEGRAM_BOT_TOKEN).") data = {k: v for k, v in payload.items() if k != "hash" and v is not None} received_hash = payload.get("hash") if not received_hash: raise AuthError("Отсутствует подпись Telegram.") check_string = "\n".join(f"{k}={data[k]}" for k in sorted(data)) secret_key = hashlib.sha256(token.encode("utf-8")).digest() computed = hmac.new( secret_key, check_string.encode("utf-8"), hashlib.sha256 ).hexdigest() if not hmac.compare_digest(computed, str(received_hash)): raise AuthError("Подпись Telegram не прошла проверку.") auth_date = int(data.get("auth_date", 0)) if auth_date and time.time() - auth_date > _MAX_AUTH_AGE_SECONDS: raise AuthError("Срок действия данных Telegram истёк.") tg_id = int(data["id"]) username = data.get("username") first = data.get("first_name", "") last = data.get("last_name", "") suggested = username or (f"{first} {last}".strip()) or str(tg_id) return ExternalIdentity( provider=self.name, external_id=str(tg_id), suggested_nickname=suggested, telegram_id=tg_id, raw=dict(payload), )