nakama/backend/utils/telegram.js

145 lines
3.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const { parse, validate } = require('@telegram-apps/init-data-node');
const crypto = require('crypto');
const config = require('../config');
const MAX_AUTH_AGE_SECONDS = 60 * 60; // 1 час
/**
* 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,
auth_date: payload?.auth_date,
authDate: payload?.authDate,
allKeys: Object.keys(payload)
});
if (!payload || !payload.user) {
throw new Error('Отсутствует пользователь в initData');
}
// Check for authDate (camelCase from library) or auth_date (snake_case)
const authDate = Number(payload.authDate || payload.auth_date);
if (!authDate) {
console.error('[Telegram] Missing authDate in payload:', payload);
throw new Error('Отсутствует auth_date в initData');
}
const now = Math.floor(Date.now() / 1000);
const age = Math.abs(now - authDate);
console.log('[Telegram] Auth date check:', {
authDate,
now,
age,
maxAge: MAX_AUTH_AGE_SECONDS,
expired: age > MAX_AUTH_AGE_SECONDS
});
if (age > MAX_AUTH_AGE_SECONDS) {
throw new Error(`Данные авторизации устарели (возраст: ${age}с, макс: ${MAX_AUTH_AGE_SECONDS}с)`);
}
console.log('[Telegram] initData validation complete');
return payload;
}
module.exports = {
validateAndParseInitData
};