we-superapp / apps /api /src /core /telegram.js
nenbuj
Initial deploy: «МЫ» супер-апп
c562e21
Raw History Blame Contribute Delete
1.88 kB
// Ядро: валидация 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;
}
}