Мобильная шапка + оплата подписки через Telegram (deep-link)
- Мобильная шапка: скрыт подзаголовок, короткая метка «Войти», бейдж ★ — не вылезает за край - /subscribe?token=…: проверка HMAC-подписи из бота → сессия tg:<id> → авто-открытие оплаты - Фронт: ?subscribe=1 открывает окно подписки после загрузки сессии Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
35
app/main.py
35
app/main.py
@@ -69,6 +69,9 @@ VK_REDIRECT = f"{os.getenv('SITE_BASE_URL', 'https://pdf2scan.online')}/api
|
||||
# ── Админ-панель ──────────────────────────────────────────────────────────────
|
||||
ADMIN_TOKEN = os.getenv("ADMIN_TOKEN", "")
|
||||
|
||||
# ── Подписка через Telegram-бота (подписанные deep-link) ──────────────────────
|
||||
SUB_LINK_SECRET = os.getenv("SUB_LINK_SECRET", "") # общий секрет бота и веба
|
||||
|
||||
# ── Вход по почте (magic-code через SMTP) ─────────────────────────────────────
|
||||
SMTP_HOST = os.getenv("SMTP_HOST", "")
|
||||
SMTP_PORT = int(os.getenv("SMTP_PORT", "465"))
|
||||
@@ -2131,6 +2134,38 @@ async def yandex_callback(request: Request, code: str = "", state: str = ""):
|
||||
return resp
|
||||
|
||||
|
||||
def _verify_sub_token(token: str):
|
||||
"""Проверяет подписанный deep-link из бота: '<tgid>.<exp>.<sig>'. Возвращает tgid (int) или None."""
|
||||
if not SUB_LINK_SECRET or not token:
|
||||
return None
|
||||
try:
|
||||
tgid_s, exp_s, sig = token.split(".", 2)
|
||||
if float(exp_s) < time.time():
|
||||
return None
|
||||
expect = hmac.new(SUB_LINK_SECRET.encode(), f"{tgid_s}.{exp_s}".encode(), hashlib.sha256).hexdigest()
|
||||
if not hmac.compare_digest(sig, expect):
|
||||
return None
|
||||
return int(tgid_s)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
@app.get("/subscribe")
|
||||
async def subscribe_via_bot(request: Request, token: str = "", name: str = ""):
|
||||
"""Deep-link из Telegram-бота: проверяем подпись, логиним как tg:<id> (сессия),
|
||||
ведём на главную с авто-открытием окна оплаты подписки."""
|
||||
tgid = _verify_sub_token(token)
|
||||
if not tgid:
|
||||
return RedirectResponse(f"{SITE_BASE_URL}/?login=error", status_code=302)
|
||||
account_id = f"tg:{tgid}"
|
||||
_db_ensure_account(account_id)
|
||||
sess = _db_create_session(account_id, "telegram", (name or "").strip()[:64] or "Telegram", "")
|
||||
resp = RedirectResponse(f"{SITE_BASE_URL}/?subscribe=1", status_code=302)
|
||||
resp.set_cookie(SESSION_COOKIE, sess, max_age=SESSION_DAYS * 86400,
|
||||
httponly=True, secure=True, samesite="lax", path="/")
|
||||
return resp
|
||||
|
||||
|
||||
@app.get("/api/me")
|
||||
async def me(request: Request):
|
||||
sess = _db_get_session(request.cookies.get(SESSION_COOKIE))
|
||||
|
||||
Reference in New Issue
Block a user