60 lines
2.2 KiB
Python
60 lines
2.2 KiB
Python
"""Telegram Login Widget провайдер.
|
|
|
|
Проверяет подпись данных виджета (HMAC-SHA256 ключом SHA256(BOT_TOKEN)) и свежесть
|
|
auth_date. Нужны TELEGRAM_BOT_TOKEN (+ TELEGRAM_BOT_USERNAME для виджета на фронте).
|
|
Доступен и в dev, и в prod (в prod — единственный метод входа).
|
|
"""
|
|
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),
|
|
)
|