mirror of
https://github.com/andrey271192/vps_monitoring.git
synced 2026-09-20 11:55:34 +00:00
fix: Telegram Mini App auth via initData (no web login needed)
- Validate Telegram WebApp initData using HMAC-SHA256 - Dual auth: cookie (web panel) OR X-Telegram-Init-Data header - Mini App sends initData with every API request - Retry button on auth failure instead of dead end Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,9 @@
|
|||||||
|
import hashlib
|
||||||
|
import hmac
|
||||||
|
import json
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
from urllib.parse import parse_qs
|
||||||
|
|
||||||
from jose import jwt
|
from jose import jwt
|
||||||
from passlib.context import CryptContext
|
from passlib.context import CryptContext
|
||||||
from fastapi import Request, HTTPException
|
from fastapi import Request, HTTPException
|
||||||
@@ -29,11 +34,54 @@ def verify_token(token: str) -> str | None:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def verify_telegram_init_data(init_data: str) -> bool:
|
||||||
|
"""Validate Telegram WebApp initData using bot token."""
|
||||||
|
settings = load_settings()
|
||||||
|
bot_token = settings.get("telegram_bot_token", "")
|
||||||
|
if not bot_token:
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
parsed = parse_qs(init_data)
|
||||||
|
received_hash = parsed.get("hash", [""])[0]
|
||||||
|
if not received_hash:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Build data-check-string
|
||||||
|
data_pairs = []
|
||||||
|
for key, values in parsed.items():
|
||||||
|
if key != "hash":
|
||||||
|
data_pairs.append(f"{key}={values[0]}")
|
||||||
|
data_pairs.sort()
|
||||||
|
data_check_string = "\n".join(data_pairs)
|
||||||
|
|
||||||
|
# HMAC-SHA256
|
||||||
|
secret_key = hmac.new(
|
||||||
|
b"WebAppData", bot_token.encode(), hashlib.sha256
|
||||||
|
).digest()
|
||||||
|
calculated_hash = hmac.new(
|
||||||
|
secret_key, data_check_string.encode(), hashlib.sha256
|
||||||
|
).hexdigest()
|
||||||
|
|
||||||
|
return calculated_hash == received_hash
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def get_current_user(request: Request) -> str | None:
|
def get_current_user(request: Request) -> str | None:
|
||||||
|
# 1. Cookie auth
|
||||||
token = request.cookies.get("access_token")
|
token = request.cookies.get("access_token")
|
||||||
if not token:
|
if token:
|
||||||
|
user = verify_token(token)
|
||||||
|
if user:
|
||||||
|
return user
|
||||||
|
|
||||||
|
# 2. Telegram initData auth (header)
|
||||||
|
tg_init = request.headers.get("X-Telegram-Init-Data")
|
||||||
|
if tg_init and verify_telegram_init_data(tg_init):
|
||||||
|
return "telegram_user"
|
||||||
|
|
||||||
return None
|
return None
|
||||||
return verify_token(token)
|
|
||||||
|
|
||||||
|
|
||||||
def require_auth(request: Request):
|
def require_auth(request: Request):
|
||||||
|
|||||||
@@ -277,9 +277,21 @@
|
|||||||
setTimeout(() => el.classList.remove('show'), 2500);
|
setTimeout(() => el.classList.remove('show'), 2500);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch with auth
|
// Telegram initData for auth
|
||||||
|
const initData = tg?.initData || '';
|
||||||
|
|
||||||
|
// Fetch with auth (cookie or Telegram initData)
|
||||||
async function api(url, opts = {}) {
|
async function api(url, opts = {}) {
|
||||||
const resp = await fetch(url, {credentials: 'include', ...opts});
|
const headers = opts.headers || {};
|
||||||
|
if (initData) {
|
||||||
|
headers['X-Telegram-Init-Data'] = initData;
|
||||||
|
}
|
||||||
|
const resp = await fetch(url, {
|
||||||
|
credentials: 'include',
|
||||||
|
...opts,
|
||||||
|
headers
|
||||||
|
});
|
||||||
|
if (!resp.ok) throw new Error(resp.status);
|
||||||
return resp.json();
|
return resp.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,7 +304,8 @@
|
|||||||
document.getElementById('app').innerHTML = `
|
document.getElementById('app').innerHTML = `
|
||||||
<div class="loading-screen">
|
<div class="loading-screen">
|
||||||
<span style="font-size:32px">🔒</span>
|
<span style="font-size:32px">🔒</span>
|
||||||
<span style="opacity:0.5;font-size:13px">Авторизуйтесь в веб-панели</span>
|
<span style="opacity:0.5;font-size:13px">Ошибка загрузки. Попробуйте /start</span>
|
||||||
|
<button onclick="loadData()" style="margin-top:12px;padding:10px 24px;border:none;border-radius:8px;background:#6366f1;color:#fff;font-size:14px;cursor:pointer">🔄 Повторить</button>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user