import crypto from 'crypto' import type { ApiRequest, ApiResponse } from './types.js' const TELEGRAM_BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN || '' const INTERNAL_API_KEY = process.env.INTERNAL_API_KEY || 'dev-internal-key' const ALLOWED_ORIGINS = [ 'https://web.telegram.org', process.env.APP_URL || null, process.env.NODE_ENV === 'development' ? 'http://localhost:5173' : null, ].filter(Boolean) as string[] // Validate Telegram Mini App initData // https://core.telegram.org/bots/webapps#validating-data-received-via-the-mini-app export function validateTelegramInitData(initData: string): { id: number; username?: string; first_name?: string; last_name?: string; language_code?: string } | null { if (!initData || !TELEGRAM_BOT_TOKEN) return null try { const params = new URLSearchParams(initData) const hash = params.get('hash') if (!hash) return null params.delete('hash') // Sort and create data-check-string const dataCheckString = Array.from(params.entries()) .sort(([a], [b]) => a.localeCompare(b)) .map(([key, value]) => `${key}=${value}`) .join('\n') // Create secret key const secretKey = crypto .createHmac('sha256', 'WebAppData') .update(TELEGRAM_BOT_TOKEN) .digest() // Compute HMAC const computedHash = crypto .createHmac('sha256', secretKey) .update(dataCheckString) .digest('hex') if (computedHash !== hash) return null // Check auth_date is not too old (24 hours) const authDate = params.get('auth_date') if (authDate) { const authTimestamp = parseInt(authDate) * 1000 const now = Date.now() if (now - authTimestamp > 24 * 60 * 60 * 1000) return null } // Parse user data const userStr = params.get('user') if (!userStr) return null return JSON.parse(userStr) } catch { return null } } // Middleware: authenticate via Telegram initData export function withTelegramAuth(handler: Function) { return async (req: ApiRequest, res: ApiResponse) => { const initData = req.headers['x-telegram-init-data'] as string const user = validateTelegramInitData(initData) if (!user) { // In development, allow bypass with query param if (process.env.NODE_ENV === 'development' && req.query.telegram_id) { req.telegramUser = { id: parseInt(req.query.telegram_id as string), first_name: 'Dev', language_code: 'ru', } return handler(req, res) } return res.status(401).json({ error: 'Invalid Telegram authentication' }) } req.telegramUser = user return handler(req, res) } } // Middleware: authenticate internal bot-to-API calls export function withInternalAuth(handler: Function) { return async (req: ApiRequest, res: ApiResponse) => { const apiKey = req.headers['x-internal-api-key'] as string if (apiKey !== INTERNAL_API_KEY) { return res.status(401).json({ error: 'Invalid internal API key' }) } return handler(req, res) } } // CORS handler export function cors(req: ApiRequest, res: ApiResponse) { const origin = req.headers.origin if (origin && ALLOWED_ORIGINS.includes(origin)) { res.setHeader('Access-Control-Allow-Origin', origin) } else if (process.env.NODE_ENV === 'development' && origin) { if (origin.startsWith('http://localhost:') || origin.startsWith('http://127.0.0.1:')) { res.setHeader('Access-Control-Allow-Origin', origin) } } res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS') res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Telegram-Init-Data, X-Internal-Api-Key') res.setHeader('Access-Control-Allow-Credentials', 'true') }