auth: fallback на initDataUnsafe для Telegram Desktop side-panel

В Telegram Desktop при открытии MiniApp в side-panel (boxed mode)
WebApp.initData приходит пустой. Backend не может проверить подпись.

Временный fallback: если initData пустой, доверяем initDataUnsafe.user
для определения роли. Action-endpoints (grant_role, measurement,
podbor) продолжают требовать подписанный initData.

Cache bust v=20260513i.
This commit is contained in:
wasrusgen 2026-05-12 21:35:51 +03:00
parent ee619bb57d
commit cb6398622b
3 changed files with 30 additions and 11 deletions

View File

@ -5,6 +5,7 @@ import json
import logging import logging
import os import os
import re import re
import time
import uuid import uuid
from datetime import datetime, timezone from datetime import datetime, timezone
from pathlib import Path from pathlib import Path
@ -427,8 +428,23 @@ def _handle_me(body: dict[str, Any]) -> dict[str, Any]:
init_data = body.get("initData") or "" init_data = body.get("initData") or ""
auth = verify_init_data(init_data, cfg.bot_token) auth = verify_init_data(init_data, cfg.bot_token)
print(f"[ME] auth result: ok={bool(auth)} user_present={bool(auth and auth.get('user'))}", flush=True, file=sys.stderr) print(f"[ME] auth result: ok={bool(auth)} user_present={bool(auth and auth.get('user'))}", flush=True, file=sys.stderr)
# Fallback для Telegram Desktop side-panel — initData может приходить пустым.
# Доверяем initDataUnsafe.user (НЕпроверенным данным) — только для UI-режима.
# Все endpoint-ы, выполняющие действия, продолжают требовать подписанный initData.
if not auth or not auth.get("user"): if not auth or not auth.get("user"):
return {"error": "invalid_init_data"} unsafe = body.get("initDataUnsafe") or {}
unsafe_user = unsafe.get("user") if isinstance(unsafe, dict) else None
if unsafe_user and unsafe_user.get("id"):
print(f"[ME] FALLBACK: using initDataUnsafe.user id={unsafe_user.get('id')}", flush=True, file=sys.stderr)
auth = {
"user": unsafe_user,
"auth_date": int(time.time()),
"start_param": unsafe.get("start_param"),
"_unsafe": True,
}
else:
return {"error": "invalid_init_data"}
tg_user = auth["user"] tg_user = auth["user"]
tg_id = tg_user["id"] tg_id = tg_user["id"]

View File

@ -63,6 +63,9 @@ async function fetchMe() {
method: "POST", method: "POST",
body: JSON.stringify({ body: JSON.stringify({
initData: tg?.initData || "", initData: tg?.initData || "",
// Fallback для Telegram Desktop side-panel где initData может приходить пустым.
// Backend проверит подпись initData первым; если её нет — упадёт сюда. UNSAFE!
initDataUnsafe: tg?.initDataUnsafe || null,
startParam: tg?.initDataUnsafe?.start_param || null, startParam: tg?.initDataUnsafe?.start_param || null,
role: explicitRole, role: explicitRole,
}), }),

View File

@ -12,8 +12,8 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Geist:wght@400;500;600&family=Newsreader:ital,wght@0,400..600;1,400..600&family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@400;500&display=swap"> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Geist:wght@400;500;600&family=Newsreader:ital,wght@0,400..600;1,400..600&family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@400;500&display=swap">
<script src="https://telegram.org/js/telegram-web-app.js"></script> <script src="https://telegram.org/js/telegram-web-app.js"></script>
<link rel="stylesheet" href="assets/styles.css?v=20260513h"> <link rel="stylesheet" href="assets/styles.css?v=20260513i">
<link rel="stylesheet" href="assets/podbor.css?v=20260513h"> <link rel="stylesheet" href="assets/podbor.css?v=20260513i">
</head> </head>
<body> <body>
<!-- Splash — за пределами #app, render-функции его не смывают --> <!-- Splash — за пределами #app, render-функции его не смывают -->
@ -34,13 +34,13 @@
</div> </div>
</div> </div>
<main id="app"></main> <main id="app"></main>
<script src="assets/icons.js?v=20260513h"></script> <script src="assets/icons.js?v=20260513i"></script>
<script src="assets/podbor.config.js?v=20260513h"></script> <script src="assets/podbor.config.js?v=20260513i"></script>
<script src="assets/podbor.picts.js?v=20260513h"></script> <script src="assets/podbor.picts.js?v=20260513i"></script>
<script src="assets/podbor.js?v=20260513h"></script> <script src="assets/podbor.js?v=20260513i"></script>
<script src="assets/clients.js?v=20260513h"></script> <script src="assets/clients.js?v=20260513i"></script>
<script src="assets/measurements.js?v=20260513h"></script> <script src="assets/measurements.js?v=20260513i"></script>
<script src="assets/request.js?v=20260513h"></script> <script src="assets/request.js?v=20260513i"></script>
<script src="assets/app.js?v=20260513h"></script> <script src="assets/app.js?v=20260513i"></script>
</body> </body>
</html> </html>