nakama/backend/utils/telegram.js

161 lines
4.6 KiB
JavaScript
Raw Permalink 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),
fullPayload: JSON.stringify(payload, null, 2)
});
if (!payload || !payload.user) {
throw new Error('Отсутствует пользователь в initData');
}
// Check if this is signature-based validation (Ed25519) or hash-based (HMAC-SHA256)
const hasSignature = 'signature' in payload;
const hasHash = 'hash' in payload;
console.log('[Telegram] Validation method:', {
hasSignature,
hasHash,
method: hasSignature ? 'Ed25519 (signature)' : 'HMAC-SHA256 (hash)'
});
// Only check auth_date for hash-based validation (old method)
// Signature-based validation (new method) doesn't include auth_date
if (hasHash && !hasSignature) {
const authDate = Number(payload.authDate || payload.auth_date);
if (!authDate) {
console.error('[Telegram] Missing authDate in hash-based 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}с)`);
}
} else if (hasSignature) {
console.log('[Telegram] Signature-based validation detected, skipping auth_date check');
}
console.log('[Telegram] initData validation complete');
return payload;
}
module.exports = {
validateAndParseInitData
};