// Ядро: валидация 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; } }