const express = require('express'); const router = express.Router(); const crypto = require('crypto'); const bcrypt = require('bcryptjs'); const axios = require('axios'); const User = require('../models/User'); const EmailVerificationCode = require('../models/EmailVerificationCode'); const { sendVerificationCode } = require('../utils/email'); const { signAuthTokens, setAuthCookies, clearAuthCookies, verifyAccessToken } = require('../utils/tokens'); const { logSecurityEvent } = require('../middleware/logger'); const { authenticateModeration } = require('../middleware/auth'); const { isEmail } = require('validator'); const config = require('../config'); // Кэш для username бота модерации let cachedModerationBotUsername = null; // Rate limiting для авторизации const rateLimit = require('express-rate-limit'); const authLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 минут max: 5, // 5 попыток message: 'Слишком много попыток авторизации. Попробуйте позже.' }); const codeLimiter = rateLimit({ windowMs: 60 * 1000, // 1 минута max: 1, // 1 запрос в минуту message: 'Подождите минуту перед следующим запросом кода.' }); // Отправка кода подтверждения на email router.post('/send-code', codeLimiter, async (req, res) => { try { const { email } = req.body; if (!email || !isEmail(email)) { return res.status(400).json({ error: 'Неверный email адрес' }); } const emailLower = email.toLowerCase().trim(); // Проверить, есть ли уже пользователь с этим email и ролью moderator/admin const existingUser = await User.findOne({ email: emailLower, role: { $in: ['moderator', 'admin'] } }); console.log(`[ModerationAuth] Проверка пользователя для email ${emailLower}:`, { found: !!existingUser, hasPassword: existingUser?.passwordHash ? true : false, role: existingUser?.role }); // Если пользователь существует - разрешить отправку кода (независимо от наличия пароля) if (existingUser) { console.log(`[ModerationAuth] Пользователь найден, отправка кода разрешена`); } else { // Пользователя нет - проверить, может быть email установлен, но роль не установлена const userByEmail = await User.findOne({ email: emailLower }); if (userByEmail) { console.log(`[ModerationAuth] Пользователь найден, но роль не moderator/admin:`, userByEmail.role); return res.status(403).json({ error: 'Регистрация недоступна. Обратитесь к администратору для получения доступа.' }); } // Если пользователя нет вообще - разрешить отправку кода // (администратор должен создать пользователя заранее, но на случай если забыл - разрешаем) console.log(`[ModerationAuth] Пользователь не найден, но отправка кода разрешена для ${emailLower}`); } // Генерировать 6-значный код const code = crypto.randomInt(100000, 999999).toString(); // Удалить старые коды для этого email await EmailVerificationCode.deleteMany({ email: email.toLowerCase(), purpose: 'registration' }); // Сохранить новый код (действителен 15 минут) const verificationCode = new EmailVerificationCode({ email: email.toLowerCase(), code, purpose: 'registration', expiresAt: new Date(Date.now() + 15 * 60 * 1000) // 15 минут }); await verificationCode.save(); // Отправить код на email try { await sendVerificationCode(email, code); res.json({ success: true, message: 'Код подтверждения отправлен на email' }); } catch (emailError) { console.error('Ошибка отправки email:', emailError); await EmailVerificationCode.deleteOne({ _id: verificationCode._id }); let errorMessage = 'Не удалось отправить код на email.'; if (emailError.code === 'EAUTH' || emailError.message?.includes('Authentication credentials invalid')) { errorMessage = 'Ошибка аутентификации SMTP. Проверьте YANDEX_SMTP_USER и YANDEX_SMTP_PASSWORD в .env. Для Yandex используйте пароль приложения, а не основной пароль.'; } else if (emailError.code === 'ECONNECTION') { errorMessage = 'Не удалось подключиться к SMTP серверу. Проверьте YANDEX_SMTP_HOST и YANDEX_SMTP_PORT.'; } else if (emailError.message) { errorMessage = `Ошибка отправки email: ${emailError.message}`; } return res.status(500).json({ error: errorMessage }); } } catch (error) { console.error('Ошибка отправки кода:', error); res.status(500).json({ error: 'Ошибка сервера' }); } }); // Регистрация с кодом подтверждения router.post('/register', authLimiter, async (req, res) => { try { const { email, code, password, username } = req.body; if (!email || !code || !password || !username) { return res.status(400).json({ error: 'Все поля обязательны' }); } if (!isEmail(email)) { return res.status(400).json({ error: 'Неверный email адрес' }); } if (password.length < 6) { return res.status(400).json({ error: 'Пароль должен содержать минимум 6 символов' }); } // Найти код подтверждения const verificationCode = await EmailVerificationCode.findOne({ email: email.toLowerCase(), code, purpose: 'registration', verified: false }); if (!verificationCode) { return res.status(400).json({ error: 'Неверный или истекший код' }); } // Проверить срок действия if (new Date() > verificationCode.expiresAt) { await EmailVerificationCode.deleteOne({ _id: verificationCode._id }); return res.status(400).json({ error: 'Код истек. Запросите новый.' }); } // Найти пользователя (должен быть создан администратором) const user = await User.findOne({ email: email.toLowerCase(), role: { $in: ['moderator', 'admin'] } }); if (!user) { return res.status(403).json({ error: 'Регистрация недоступна. Обратитесь к администратору.' }); } // Если у пользователя уже есть пароль - ошибка if (user.passwordHash) { return res.status(400).json({ error: 'Аккаунт уже зарегистрирован. Используйте вход по паролю.' }); } // Захешировать пароль const passwordHash = await bcrypt.hash(password, 10); // Обновить пользователя user.passwordHash = passwordHash; user.emailVerified = true; user.username = username || user.username; await user.save(); // Пометить код как использованный verificationCode.verified = true; await verificationCode.save(); // Генерировать токены const tokens = signAuthTokens(user); // Установить cookies setAuthCookies(res, tokens); res.json({ success: true, user: { id: user._id, username: user.username, role: user.role }, accessToken: tokens.accessToken }); } catch (error) { console.error('Ошибка регистрации:', error); res.status(500).json({ error: 'Ошибка сервера' }); } }); // Авторизация по email и паролю router.post('/login', authLimiter, async (req, res) => { try { const { email, password } = req.body; if (!email || !password) { return res.status(400).json({ error: 'Email и пароль обязательны' }); } if (!isEmail(email)) { return res.status(400).json({ error: 'Неверный email адрес' }); } // Найти пользователя с паролем (только модераторы и админы) const user = await User.findOne({ email: email.toLowerCase(), passwordHash: { $exists: true, $ne: null }, role: { $in: ['moderator', 'admin'] } }).select('+passwordHash'); if (!user) { logSecurityEvent('MODERATION_LOGIN_FAILED', req, { email: email.toLowerCase() }); return res.status(401).json({ error: 'Неверный email или пароль' }); } if (user.banned) { return res.status(403).json({ error: 'Аккаунт заблокирован' }); } // Проверить пароль const isPasswordValid = await bcrypt.compare(password, user.passwordHash); if (!isPasswordValid) { logSecurityEvent('MODERATION_LOGIN_FAILED', req, { email: email.toLowerCase(), userId: user._id }); return res.status(401).json({ error: 'Неверный email или пароль' }); } // Обновить время последней активности user.lastActiveAt = new Date(); await user.save(); // Генерировать токены const tokens = signAuthTokens(user); // Установить cookies setAuthCookies(res, tokens); logSecurityEvent('MODERATION_LOGIN_SUCCESS', req, { userId: user._id, email: user.email }); // Email не возвращаем в ответе для безопасности res.json({ success: true, user: { id: user._id, username: user.username, role: user.role, telegramId: user.telegramId }, accessToken: tokens.accessToken }); } catch (error) { console.error('Ошибка авторизации:', error); res.status(500).json({ error: 'Ошибка сервера' }); } }); // Авторизация через Telegram Login Widget (для обычного браузера) router.post('/telegram-widget', authLimiter, async (req, res) => { try { const { id, first_name, last_name, username, photo_url, auth_date, hash } = req.body; if (!id || !hash || !auth_date) { return res.status(400).json({ error: 'Неполные данные от Telegram Login Widget' }); } // Проверить подпись (базовая проверка) // В production нужно проверить hash через Bot API // Для модерации используем упрощенную проверку - ищем пользователя по telegramId const user = await User.findOne({ telegramId: id.toString() }); if (!user) { return res.status(404).json({ error: 'Пользователь не найден. Сначала зарегистрируйтесь через бота.' }); } if (!['moderator', 'admin'].includes(user.role)) { return res.status(403).json({ error: 'Доступ запрещен. У вас нет прав модератора.' }); } if (user.banned) { return res.status(403).json({ error: 'Аккаунт заблокирован' }); } // Обновить данные пользователя из виджета if (username && !user.username) { user.username = username; } if (first_name && !user.firstName) { user.firstName = first_name; } if (last_name && !user.lastName) { user.lastName = last_name; } if (photo_url && !user.photoUrl) { user.photoUrl = photo_url; } user.lastActiveAt = new Date(); await user.save(); // Генерировать JWT токены const tokens = signAuthTokens(user); setAuthCookies(res, tokens); logSecurityEvent('MODERATION_TELEGRAM_WIDGET_LOGIN_SUCCESS', req, { userId: user._id }); res.json({ success: true, user: { id: user._id, username: user.username, role: user.role, telegramId: user.telegramId }, accessToken: tokens.accessToken }); } catch (error) { console.error('Ошибка авторизации через Telegram Widget:', error); res.status(500).json({ error: 'Ошибка сервера' }); } }); // Авторизация через Telegram (для модерации) router.post('/telegram', authLimiter, authenticateModeration, async (req, res) => { try { const user = req.user; const { isModerationAdmin } = require('../services/moderationAdmin'); const { normalizeUsername } = require('../services/moderationAdmin'); const config = require('../config'); if (!user) { return res.status(403).json({ error: 'Доступ запрещен' }); } if (user.banned) { return res.status(403).json({ error: 'Аккаунт заблокирован' }); } // Проверить доступ: роль admin/moderator ИЛИ является модератором через ModerationAdmin const username = normalizeUsername(user.username); const telegramId = user.telegramId; const OWNER_USERNAMES = new Set(config.moderationOwnerUsernames || []); const isOwner = OWNER_USERNAMES.has(username); const isAdminByRole = ['moderator', 'admin'].includes(user.role); const isAdminByDB = await isModerationAdmin({ telegramId, username }); if (!isOwner && !isAdminByRole && !isAdminByDB) { return res.status(403).json({ error: 'Доступ запрещен. У вас нет прав модератора. Обратитесь к администратору.' }); } // Обновить время последней активности user.lastActiveAt = new Date(); await user.save(); // Генерировать токены const tokens = signAuthTokens(user); // Установить cookies setAuthCookies(res, tokens); logSecurityEvent('MODERATION_TELEGRAM_LOGIN_SUCCESS', req, { userId: user._id }); // Email не возвращаем в ответе для безопасности res.json({ success: true, user: { id: user._id, username: user.username, role: user.role, telegramId: user.telegramId }, accessToken: tokens.accessToken }); } catch (error) { console.error('Ошибка авторизации через Telegram:', error); res.status(500).json({ error: 'Ошибка сервера' }); } }); // Выход router.post('/logout', (req, res) => { clearAuthCookies(res); res.json({ success: true }); }); // Получить конфигурацию для фронтенда (включая bot username) router.get('/config', async (req, res) => { try { // Если username указан в env - используем его if (config.moderationBotUsername) { return res.json({ botUsername: config.moderationBotUsername }); } // Если есть кэш - используем его if (cachedModerationBotUsername) { return res.json({ botUsername: cachedModerationBotUsername }); } // Получить username через Bot API используя MODERATION_BOT_TOKEN if (config.moderationBotToken) { try { const botInfo = await axios.get(`https://api.telegram.org/bot${config.moderationBotToken}/getMe`); const username = botInfo.data.result?.username; if (username) { cachedModerationBotUsername = username; return res.json({ botUsername: username }); } } catch (error) { console.error('Ошибка получения username бота модерации через Bot API:', error.message); } } // Fallback res.json({ botUsername: 'moderation_bot' }); } catch (error) { console.error('Ошибка получения конфигурации:', error); res.json({ botUsername: cachedModerationBotUsername || 'moderation_bot' }); } }); // Проверка текущей сессии router.get('/me', async (req, res) => { try { // Получить токен из заголовка или cookie let token = null; const authHeader = req.headers.authorization; if (authHeader && authHeader.startsWith('Bearer ')) { token = authHeader.slice(7); } else if (req.cookies && req.cookies[require('../utils/tokens').ACCESS_COOKIE]) { token = req.cookies[require('../utils/tokens').ACCESS_COOKIE]; } if (!token) { return res.status(401).json({ error: 'Не авторизован' }); } // Проверить токен let payload; try { payload = verifyAccessToken(token); } catch (error) { return res.status(401).json({ error: 'Неверный токен' }); } // Найти пользователя const user = await User.findById(payload.userId); if (!user) { return res.status(401).json({ error: 'Пользователь не найден' }); } if (user.banned) { return res.status(403).json({ error: 'Аккаунт заблокирован' }); } // Проверить роль (только модераторы и админы) if (!['moderator', 'admin'].includes(user.role)) { return res.status(403).json({ error: 'Доступ запрещен' }); } res.json({ success: true, user: { id: user._id, username: user.username, role: user.role, telegramId: user.telegramId } }); } catch (error) { console.error('Ошибка проверки сессии:', error); res.status(500).json({ error: 'Ошибка сервера' }); } }); module.exports = router;