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');
|
|
|
|
|
|
|
2025-11-10 23:16:43 +00:00
|
|
|
|
const MAX_AUTH_AGE_SECONDS = 60 * 60; // 1 час
|
2025-11-10 21:56:36 +00:00
|
|
|
|
|
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 23:11:33 +00:00
|
|
|
|
function validateAndParseInitData(initDataRaw, botToken = null) {
|
|
|
|
|
|
const tokenToUse = botToken || config.telegramBotToken;
|
|
|
|
|
|
|
2025-11-10 23:04:30 +00:00
|
|
|
|
console.log('[Telegram] validateAndParseInitData called:', {
|
|
|
|
|
|
hasInitData: !!initDataRaw,
|
|
|
|
|
|
type: typeof initDataRaw,
|
|
|
|
|
|
length: initDataRaw?.length || 0,
|
2025-11-10 23:11:33 +00:00
|
|
|
|
preview: initDataRaw?.substring(0, 100) + '...',
|
|
|
|
|
|
usingModerationToken: !!botToken
|
2025-11-10 23:04:30 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
2025-11-10 23:11:33 +00:00
|
|
|
|
if (!tokenToUse) {
|
|
|
|
|
|
throw new Error('Bot token не настроен');
|
2025-11-10 21:56:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
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 {
|
2025-11-10 23:11:33 +00:00
|
|
|
|
validate(trimmed, tokenToUse);
|
2025-11-10 23:04:30 +00:00
|
|
|
|
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 {
|
2025-11-10 23:11:33 +00:00
|
|
|
|
valid = manualValidateInitData(trimmed, tokenToUse);
|
2025-11-10 23:04:30 +00:00
|
|
|
|
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,
|
2025-11-10 23:16:43 +00:00
|
|
|
|
auth_date: payload?.auth_date,
|
|
|
|
|
|
authDate: payload?.authDate,
|
2025-11-10 23:22:34 +00:00
|
|
|
|
allKeys: Object.keys(payload),
|
|
|
|
|
|
fullPayload: JSON.stringify(payload, null, 2)
|
2025-11-10 23:04:30 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
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 23:22:34 +00:00
|
|
|
|
// Check if this is signature-based validation (Ed25519) or hash-based (HMAC-SHA256)
|
|
|
|
|
|
const hasSignature = 'signature' in payload;
|
|
|
|
|
|
const hasHash = 'hash' in payload;
|
2025-11-10 22:19:37 +00:00
|
|
|
|
|
2025-11-10 23:22:34 +00:00
|
|
|
|
console.log('[Telegram] Validation method:', {
|
|
|
|
|
|
hasSignature,
|
|
|
|
|
|
hasHash,
|
|
|
|
|
|
method: hasSignature ? 'Ed25519 (signature)' : 'HMAC-SHA256 (hash)'
|
|
|
|
|
|
});
|
2025-11-10 21:56:36 +00:00
|
|
|
|
|
2025-11-10 23:22:34 +00:00
|
|
|
|
// 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);
|
2025-11-10 23:16:43 +00:00
|
|
|
|
|
2025-11-10 23:22:34 +00:00
|
|
|
|
if (!authDate) {
|
|
|
|
|
|
console.error('[Telegram] Missing authDate in hash-based payload:', payload);
|
|
|
|
|
|
throw new Error('Отсутствует auth_date в initData');
|
|
|
|
|
|
}
|
2025-11-10 22:19:37 +00:00
|
|
|
|
|
2025-11-10 23:22:34 +00:00
|
|
|
|
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');
|
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
|
|
|
|
|
|
};
|
|
|
|
|
|
|