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, botToken = null) { const tokenToUse = botToken || config.telegramBotToken; console.log('[Telegram] validateAndParseInitData called:', { hasInitData: !!initDataRaw, type: typeof initDataRaw, length: initDataRaw?.length || 0, preview: initDataRaw?.substring(0, 100) + '...', usingModerationToken: !!botToken }); if (!tokenToUse) { throw new Error('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, tokenToUse); 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, tokenToUse); 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, allKeys: Object.keys(payload) }); if (!payload || !payload.user) { throw new Error('Отсутствует пользователь в initData'); } // Check for auth_date - it might be authDate in parsed payload const authDate = Number(payload.auth_date || payload.authDate); if (!authDate) { console.error('[Telegram] Missing auth_date in payload:', payload); 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 };