const { parse, validate } = require('@telegram-apps/init-data-node'); const crypto = require('crypto'); const config = require('../config'); const MAX_AUTH_AGE_SECONDS = 5 * 60; /** * Manual validation with base64 padding fix * Based on: https://docs.telegram-mini-apps.com/platform/init-data */ function manualValidateInitData(initDataRaw, botToken) { const params = new URLSearchParams(initDataRaw); const hash = params.get('hash'); if (!hash) { throw new Error('Отсутствует hash в initData'); } // Remove hash from params params.delete('hash'); // Create data check string const dataCheckArr = Array.from(params.entries()) .sort(([a], [b]) => a.localeCompare(b)) .map(([key, value]) => `${key}=${value}`); const dataCheckString = dataCheckArr.join('\n'); // Create secret key const secretKey = crypto .createHmac('sha256', 'WebAppData') .update(botToken) .digest(); // Create signature const signature = crypto .createHmac('sha256', secretKey) .update(dataCheckString) .digest('hex'); // Compare signatures return signature === hash; } function validateAndParseInitData(initDataRaw) { console.log('[Telegram] validateAndParseInitData called:', { hasInitData: !!initDataRaw, type: typeof initDataRaw, length: initDataRaw?.length || 0, preview: initDataRaw?.substring(0, 100) + '...' }); if (!config.telegramBotToken) { throw new Error('TELEGRAM_BOT_TOKEN не настроен'); } if (!initDataRaw || typeof initDataRaw !== 'string') { throw new Error('initData не передан'); } const trimmed = initDataRaw.trim(); if (!trimmed.length) { throw new Error('initData пуст'); } console.log('[Telegram] Validating initData with bot token...'); // Try library validation first let valid = false; try { validate(trimmed, config.telegramBotToken); valid = true; console.log('[Telegram] Library validation successful'); } catch (libError) { console.log('[Telegram] Library validation failed, trying manual validation:', libError.message); // Fallback to manual validation with base64 padding fix try { valid = manualValidateInitData(trimmed, config.telegramBotToken); if (valid) { console.log('[Telegram] Manual validation successful'); } } catch (manualError) { console.error('[Telegram] Manual validation also failed:', manualError.message); } } if (!valid) { console.error('[Telegram] All validation attempts failed'); throw new Error('Неверная подпись initData'); } console.log('[Telegram] initData validation successful, parsing...'); const payload = parse(trimmed); console.log('[Telegram] Parsed payload:', { hasUser: !!payload?.user, userId: payload?.user?.id, authDate: payload?.auth_date }); if (!payload || !payload.user) { throw new Error('Отсутствует пользователь в initData'); } const authDate = Number(payload.auth_date); if (!authDate) { throw new Error('Отсутствует auth_date в initData'); } const now = Math.floor(Date.now() / 1000); if (Math.abs(now - authDate) > MAX_AUTH_AGE_SECONDS) { throw new Error('Данные авторизации устарели'); } console.log('[Telegram] initData validation complete'); return payload; } module.exports = { validateAndParseInitData };