nakama/backend/utils/telegram.js

129 lines
3.4 KiB
JavaScript
Raw Normal View History

2025-11-10 23:04:30 +00:00
const { parse, validate } = require('@telegram-apps/init-data-node');
const crypto = require('crypto');
2025-11-10 21:56:36 +00:00
const config = require('../config');
const MAX_AUTH_AGE_SECONDS = 5 * 60;
2025-11-10 23:04:30 +00:00
/**
* 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;
}
2025-11-10 22:19:37 +00:00
function validateAndParseInitData(initDataRaw) {
2025-11-10 23:04:30 +00:00
console.log('[Telegram] validateAndParseInitData called:', {
hasInitData: !!initDataRaw,
type: typeof initDataRaw,
length: initDataRaw?.length || 0,
preview: initDataRaw?.substring(0, 100) + '...'
});
2025-11-10 21:56:36 +00:00
if (!config.telegramBotToken) {
throw new Error('TELEGRAM_BOT_TOKEN не настроен');
}
2025-11-10 22:19:37 +00:00
if (!initDataRaw || typeof initDataRaw !== 'string') {
throw new Error('initData не передан');
2025-11-10 21:56:36 +00:00
}
2025-11-10 22:19:37 +00:00
const trimmed = initDataRaw.trim();
2025-11-10 21:56:36 +00:00
2025-11-10 22:19:37 +00:00
if (!trimmed.length) {
throw new Error('initData пуст');
2025-11-10 21:56:36 +00:00
}
2025-11-10 23:04:30 +00:00
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);
}
}
2025-11-10 21:56:36 +00:00
2025-11-10 22:19:37 +00:00
if (!valid) {
2025-11-10 23:04:30 +00:00
console.error('[Telegram] All validation attempts failed');
2025-11-10 21:56:36 +00:00
throw new Error('Неверная подпись initData');
}
2025-11-10 23:04:30 +00:00
console.log('[Telegram] initData validation successful, parsing...');
2025-11-10 22:19:37 +00:00
const payload = parse(trimmed);
2025-11-10 21:56:36 +00:00
2025-11-10 23:04:30 +00:00
console.log('[Telegram] Parsed payload:', {
hasUser: !!payload?.user,
userId: payload?.user?.id,
authDate: payload?.auth_date
});
2025-11-10 22:19:37 +00:00
if (!payload || !payload.user) {
2025-11-10 21:56:36 +00:00
throw new Error('Отсутствует пользователь в initData');
}
2025-11-10 22:19:37 +00:00
const authDate = Number(payload.auth_date);
if (!authDate) {
throw new Error('Отсутствует auth_date в initData');
2025-11-10 21:56:36 +00:00
}
2025-11-10 22:19:37 +00:00
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - authDate) > MAX_AUTH_AGE_SECONDS) {
throw new Error('Данные авторизации устарели');
2025-11-10 21:56:36 +00:00
}
2025-11-10 23:04:30 +00:00
console.log('[Telegram] initData validation complete');
2025-11-10 22:19:37 +00:00
return payload;
2025-11-10 21:56:36 +00:00
}
module.exports = {
validateAndParseInitData
};