Spaces:
Paused
Paused
File size: 1,882 Bytes
c562e21 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | // Ядро: валидация Telegram WebApp initData. Ведёт Claude.
// Алгоритм по докам Telegram:
// secret_key = HMAC_SHA256(key="WebAppData", message=BOT_TOKEN)
// hash = HMAC_SHA256(key=secret_key, message=data_check_string)
// data_check_string — пары "key=value" (без hash), отсортированные по ключу, через '\n'.
import crypto from 'node:crypto';
export const BOT_TOKEN = process.env.BOT_TOKEN || '';
// Возвращает объект пользователя Telegram при валидной подписи, иначе null.
// maxAgeSec — защита от старых initData (по умолчанию 24ч).
export function validateInitData(initData, maxAgeSec = 86400) {
if (!BOT_TOKEN || !initData) return null;
const params = new URLSearchParams(initData);
const hash = params.get('hash');
if (!hash) return null;
params.delete('hash');
const dataCheckString = [...params.entries()]
.map(([k, v]) => `${k}=${v}`)
.sort()
.join('\n');
const secretKey = crypto.createHmac('sha256', 'WebAppData').update(BOT_TOKEN).digest();
const computed = crypto.createHmac('sha256', secretKey).update(dataCheckString).digest('hex');
// сравнение в постоянное время
const a = Buffer.from(computed, 'hex');
const b = Buffer.from(hash, 'hex');
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return null;
// проверка свежести
const authDate = Number(params.get('auth_date') || 0);
if (authDate && Date.now() / 1000 - authDate > maxAgeSec) return null;
// user приходит JSON-строкой
try {
const user = JSON.parse(params.get('user') || 'null');
if (!user || !user.id) return null;
return user; // { id, first_name, last_name?, username?, ... }
} catch {
return null;
}
}
|