const crypto = require('crypto'); const User = require('../models/User'); const { validateTelegramId } = require('./validator'); const { logSecurityEvent } = require('./logger'); const config = require('../config'); const OFFICIAL_CLIENT_MESSAGE = 'Используйте официальный клиент. Сообщите об ошибке в https://t.me/NakamaReportbot'; const ALLOWED_SEARCH_PREFERENCES = ['furry', 'anime']; const MAX_AUTH_AGE_SECONDS = 5 * 60; // 5 минут const touchUserActivity = async (user) => { if (!user) return; const now = Date.now(); const shouldUpdate = !user.lastActiveAt || Math.abs(now - new Date(user.lastActiveAt).getTime()) > 5 * 60 * 1000; if (shouldUpdate) { user.lastActiveAt = new Date(now); await user.save(); } }; const ensureUserSettings = async (user) => { if (!user) return; let updated = false; if (!user.settings) { user.settings = {}; updated = true; } if (!ALLOWED_SEARCH_PREFERENCES.includes(user.settings.searchPreference)) { user.settings.searchPreference = 'furry'; updated = true; } if (!user.settings.whitelist) { user.settings.whitelist = { noNSFW: true }; updated = true; } else if (user.settings.whitelist.noNSFW === undefined) { user.settings.whitelist.noNSFW = true; updated = true; } if (updated) { user.markModified('settings'); await user.save(); } }; function validateTelegramWebAppData(initData, botToken) { if (!botToken) { throw new Error('TELEGRAM_BOT_TOKEN не настроен'); } const params = new URLSearchParams(initData); const hash = params.get('hash'); const authDate = Number(params.get('auth_date')); if (!hash) { throw new Error('Отсутствует hash в initData'); } if (!authDate) { throw new Error('Отсутствует auth_date в initData'); } const dataCheck = []; for (const [key, value] of params.entries()) { if (key === 'hash') continue; dataCheck.push(`${key}=${value}`); } dataCheck.sort((a, b) => a.localeCompare(b)); const dataCheckString = dataCheck.join('\n'); const secretKey = crypto.createHmac('sha256', 'WebAppData').update(botToken).digest(); const calculatedHash = crypto.createHmac('sha256', secretKey).update(dataCheckString).digest('hex'); if (calculatedHash !== hash) { throw new Error('Неверная подпись initData'); } const now = Math.floor(Date.now() / 1000); if (Math.abs(now - authDate) > MAX_AUTH_AGE_SECONDS) { throw new Error('Данные авторизации устарели'); } return params; } // Middleware для проверки авторизации const authenticate = async (req, res, next) => { try { const initData = req.headers['x-telegram-init-data']; if (!initData) { logSecurityEvent('MISSING_INITDATA', req); return res.status(401).json({ error: OFFICIAL_CLIENT_MESSAGE }); } if (!config.telegramBotToken) { logSecurityEvent('MISSING_BOT_TOKEN', req); if (config.isProduction()) { return res.status(500).json({ error: 'Сервер некорректно настроен для авторизации через Telegram' }); } console.warn('⚠️ TELEGRAM_BOT_TOKEN не установлен, пропускаем проверку подписи (dev режим)'); } let params; try { if (config.telegramBotToken) { params = validateTelegramWebAppData(initData, config.telegramBotToken); } else { params = new URLSearchParams(initData); } } catch (validationError) { logSecurityEvent('INVALID_INITDATA_SIGNATURE', req, { reason: validationError.message }); return res.status(401).json({ error: `${validationError.message}. ${OFFICIAL_CLIENT_MESSAGE}` }); } const userParam = params.get('user'); if (!userParam) { logSecurityEvent('MISSING_INITDATA_USER', req); return res.status(401).json({ error: OFFICIAL_CLIENT_MESSAGE }); } let telegramUser; try { telegramUser = JSON.parse(userParam); } catch (parseError) { logSecurityEvent('INVALID_INITDATA_USER_JSON', req, { error: parseError.message }); return res.status(401).json({ error: OFFICIAL_CLIENT_MESSAGE }); } req.telegramUser = telegramUser; if (!validateTelegramId(telegramUser.id)) { logSecurityEvent('INVALID_TELEGRAM_ID', req, { telegramId: telegramUser.id }); return res.status(401).json({ error: 'Неверный ID пользователя' }); } // Найти или создать пользователя let user = await User.findOne({ telegramId: telegramUser.id.toString() }); if (!user) { user = new User({ telegramId: telegramUser.id.toString(), username: telegramUser.username || telegramUser.first_name, firstName: telegramUser.first_name, lastName: telegramUser.last_name, photoUrl: telegramUser.photo_url }); await user.save(); console.log(`✅ Создан новый пользователь: ${user.username}`); } else { user.username = telegramUser.username || telegramUser.first_name; user.firstName = telegramUser.first_name; user.lastName = telegramUser.last_name; if (telegramUser.photo_url) { user.photoUrl = telegramUser.photo_url; } await user.save(); } await ensureUserSettings(user); await touchUserActivity(user); req.user = user; next(); } catch (error) { console.error('❌ Ошибка авторизации:', error); res.status(401).json({ error: `Ошибка авторизации. ${OFFICIAL_CLIENT_MESSAGE}` }); } }; // Middleware для проверки роли модератора const requireModerator = (req, res, next) => { if (req.user.role !== 'moderator' && req.user.role !== 'admin') { return res.status(403).json({ error: 'Требуются права модератора' }); } next(); }; // Middleware для проверки роли админа const requireAdmin = (req, res, next) => { if (req.user.role !== 'admin') { return res.status(403).json({ error: 'Требуются права администратора' }); } next(); }; module.exports = { authenticate, requireModerator, requireAdmin };