import { createHmac, timingSafeEqual } from 'node:crypto'; import { config } from '../config.js'; import { prisma } from '../prisma.js'; type TelegramInitDataUser = { id: number; username?: string; first_name?: string; last_name?: string; photo_url?: string; language_code?: string; }; function hmacSha256(key: string | Buffer, value: string) { return createHmac('sha256', key).update(value).digest(); } function assertValidHash(receivedHash: string, expectedHash: Buffer) { const received = Buffer.from(receivedHash, 'hex'); if (received.length !== expectedHash.length) { throw new Error('Telegram init data hash is invalid.'); } if (!timingSafeEqual(received, expectedHash)) { throw new Error('Telegram init data hash is invalid.'); } } function parseTelegramInitData(initData: string) { if (!config.telegramMiniAppBotToken) { throw new Error('TELEGRAM_MINI_APP_BOT_TOKEN is required.'); } const params = new URLSearchParams(initData); const receivedHash = params.get('hash'); if (!receivedHash) { throw new Error('Telegram init data hash is required.'); } const authDate = Number(params.get('auth_date')); if (!Number.isFinite(authDate)) { throw new Error('Telegram auth_date is required.'); } const ageSeconds = Math.floor(Date.now() / 1000) - authDate; if (ageSeconds > config.telegramAuthMaxAgeSeconds) { throw new Error('Telegram init data is expired.'); } const dataCheckString = [...params.entries()] .filter(([key]) => key !== 'hash') .sort(([left], [right]) => left.localeCompare(right)) .map(([key, value]) => `${key}=${value}`) .join('\n'); const secretKey = hmacSha256('WebAppData', config.telegramMiniAppBotToken); const expectedHash = hmacSha256(secretKey, dataCheckString); assertValidHash(receivedHash, expectedHash); const rawUser = params.get('user'); if (!rawUser) { throw new Error('Telegram user is required.'); } return JSON.parse(rawUser) as TelegramInitDataUser; } export async function getOrCreateTelegramUser(initData: string) { const user = parseTelegramInitData(initData); return prisma.user.upsert({ where: { telegramId: String(user.id) }, create: { telegramId: String(user.id), username: user.username, firstName: user.first_name, lastName: user.last_name, photoUrl: user.photo_url, languageCode: user.language_code, }, update: { username: user.username, firstName: user.first_name, lastName: user.last_name, photoUrl: user.photo_url, languageCode: user.language_code, }, }); } export async function requireTelegramUser(initData?: string) { if (!initData) { throw new Error('Telegram authorization is required.'); } return getOrCreateTelegramUser(initData); }