const express = require('express'); const router = express.Router(); const crypto = require('crypto'); const User = require('../models/User'); const config = require('../config'); const { validateTelegramId } = require('../middleware/validator'); const { logSecurityEvent } = require('../middleware/logger'); const { strictAuthLimiter } = require('../middleware/security'); const ALLOWED_SEARCH_PREFERENCES = ['furry', 'anime']; const normalizeUserSettings = (settings = {}) => { const plainSettings = typeof settings.toObject === 'function' ? settings.toObject() : { ...settings }; const whitelistSource = plainSettings.whitelist; const whitelist = whitelistSource && typeof whitelistSource.toObject === 'function' ? whitelistSource.toObject() : { ...(whitelistSource || {}) }; return { ...plainSettings, whitelist: { noNSFW: whitelist?.noNSFW ?? true, ...whitelist }, searchPreference: ALLOWED_SEARCH_PREFERENCES.includes(plainSettings.searchPreference) ? plainSettings.searchPreference : 'furry' }; }; const OFFICIAL_CLIENT_MESSAGE = 'Используйте официальный клиент. Сообщите об ошибке в https://t.me/NakamaReportbot'; // Проверка подписи Telegram OAuth (Login Widget) function validateTelegramOAuth(authData, botToken) { if (!authData || !authData.hash) { return false; } const { hash, ...data } = authData; // Удалить поля с undefined/null значениями (они не должны быть в dataCheckString) const cleanData = {}; for (const key in data) { if (data[key] !== undefined && data[key] !== null && data[key] !== '') { cleanData[key] = data[key]; } } // Формировать dataCheckString из очищенных данных const dataCheckString = Object.keys(cleanData) .sort() .map(key => `${key}=${cleanData[key]}`) .join('\n'); const secretKey = crypto .createHmac('sha256', 'WebAppData') .update(botToken) .digest(); const calculatedHash = crypto .createHmac('sha256', secretKey) .update(dataCheckString) .digest('hex'); return calculatedHash === hash; } // Авторизация через Telegram OAuth (Login Widget) router.post('/oauth', strictAuthLimiter, async (req, res) => { try { const { user: telegramUser, auth_date, hash } = req.body; if (!telegramUser || !auth_date || !hash) { logSecurityEvent('INVALID_OAUTH_DATA', req); return res.status(400).json({ error: 'Неверные данные авторизации' }); } // Валидация Telegram ID if (!validateTelegramId(telegramUser.id)) { logSecurityEvent('INVALID_TELEGRAM_ID', req, { telegramId: telegramUser.id }); return res.status(400).json({ error: 'Неверный ID пользователя' }); } // Проверка подписи Telegram (строгая проверка в production) if (config.telegramBotToken) { // Формировать authData только с присутствующими полями const authData = { id: telegramUser.id, first_name: telegramUser.first_name || '', auth_date: auth_date.toString(), hash: hash }; // Добавить опциональные поля только если они присутствуют if (telegramUser.last_name) { authData.last_name = telegramUser.last_name; } if (telegramUser.username) { authData.username = telegramUser.username; } if (telegramUser.photo_url) { authData.photo_url = telegramUser.photo_url; } const isValid = validateTelegramOAuth(authData, config.telegramBotToken); if (!isValid) { logSecurityEvent('INVALID_OAUTH_SIGNATURE', req, { telegramId: telegramUser.id, receivedData: { id: telegramUser.id, first_name: telegramUser.first_name, last_name: telegramUser.last_name, username: telegramUser.username, auth_date: auth_date } }); // В production строгая проверка, но для отладки можно временно отключить if (config.isProduction()) { // Временно разрешить в production для отладки (можно вернуть строгую проверку) console.warn('⚠️ OAuth signature validation failed, but allowing in production for debugging'); return res.status(401).json({ error: 'Неверная подпись Telegram OAuth' }); } } } // Найти или создать пользователя 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(`✅ Создан новый пользователь через OAuth: ${user.username}`); } else { // Обновить данные пользователя user.username = telegramUser.username || telegramUser.first_name; user.firstName = telegramUser.first_name; user.lastName = telegramUser.last_name; user.photoUrl = telegramUser.photo_url; await user.save(); } // Получить полные данные пользователя const populatedUser = await User.findById(user._id).populate([ { path: 'followers', select: 'username firstName lastName photoUrl' }, { path: 'following', select: 'username firstName lastName photoUrl' } ]); const settings = normalizeUserSettings(populatedUser.settings); res.json({ success: true, user: { id: populatedUser._id, telegramId: populatedUser.telegramId, username: populatedUser.username, firstName: populatedUser.firstName, lastName: populatedUser.lastName, photoUrl: populatedUser.photoUrl, bio: populatedUser.bio, role: populatedUser.role, followersCount: populatedUser.followers.length, followingCount: populatedUser.following.length, settings, banned: populatedUser.banned } }); } catch (error) { console.error('Ошибка OAuth:', error); res.status(500).json({ error: 'Ошибка сервера' }); } }); // Проверка авторизации и получение данных пользователя const { authenticate } = require('../middleware/auth'); router.post('/verify', authenticate, async (req, res) => { try { const user = await req.user.populate([ { path: 'followers', select: 'username firstName lastName photoUrl' }, { path: 'following', select: 'username firstName lastName photoUrl' } ]); const settings = normalizeUserSettings(user.settings); res.json({ success: true, user: { id: user._id, telegramId: user.telegramId, username: user.username, firstName: user.firstName, lastName: user.lastName, photoUrl: user.photoUrl, bio: user.bio, role: user.role, followersCount: user.followers.length, followingCount: user.following.length, settings, banned: user.banned } }); } catch (error) { console.error('Ошибка verify:', error); res.status(500).json({ error: 'Ошибка сервера' }); } }); // Проверка сохраненной сессии по telegramId (для OAuth пользователей) router.post('/session', async (req, res) => { try { const { telegramId } = req.body; if (!telegramId) { return res.status(400).json({ error: 'Не указан telegramId' }); } // Найти пользователя по telegramId const user = await User.findOne({ telegramId: telegramId.toString() }); if (!user) { return res.status(404).json({ error: OFFICIAL_CLIENT_MESSAGE }); } if (user.banned) { return res.status(403).json({ error: 'Пользователь заблокирован' }); } // Получить полные данные пользователя const populatedUser = await User.findById(user._id).populate([ { path: 'followers', select: 'username firstName lastName photoUrl' }, { path: 'following', select: 'username firstName lastName photoUrl' } ]); res.json({ success: true, user: { id: populatedUser._id, telegramId: populatedUser.telegramId, username: populatedUser.username, firstName: populatedUser.firstName, lastName: populatedUser.lastName, photoUrl: populatedUser.photoUrl, bio: populatedUser.bio, role: populatedUser.role, followersCount: populatedUser.followers.length, followingCount: populatedUser.following.length, settings, banned: populatedUser.banned } }); } catch (error) { console.error('Ошибка проверки сессии:', error); res.status(500).json({ error: 'Ошибка сервера' }); } }); module.exports = router;