From c3f27467230c627fa2c935ad3db6ac84b9d7971d Mon Sep 17 00:00:00 2001 From: glpshchn <464976@niuitmo.ru> Date: Tue, 11 Nov 2025 02:11:33 +0300 Subject: [PATCH] Update files --- backend/middleware/auth.js | 79 ++++++++++++++++++++++++++++++++++++++ backend/routes/modApp.js | 24 ++++++------ backend/utils/telegram.js | 22 +++++++---- 3 files changed, 105 insertions(+), 20 deletions(-) diff --git a/backend/middleware/auth.js b/backend/middleware/auth.js index 5fdf166..ff91ade 100644 --- a/backend/middleware/auth.js +++ b/backend/middleware/auth.js @@ -142,8 +142,87 @@ const requireAdmin = (req, res, next) => { next(); }; +// Middleware для модерации (использует MODERATION_BOT_TOKEN) +const authenticateModeration = async (req, res, next) => { + const config = require('../config'); + + try { + const authHeader = req.headers.authorization || ''; + let initDataRaw = null; + + if (authHeader.startsWith('tma ')) { + initDataRaw = authHeader.slice(4).trim(); + } + + if (!initDataRaw) { + const headerInitData = req.headers['x-telegram-init-data']; + if (headerInitData && typeof headerInitData === 'string') { + initDataRaw = headerInitData.trim(); + } + } + + if (!initDataRaw) { + logSecurityEvent('AUTH_TOKEN_MISSING', req); + return res.status(401).json({ error: OFFICIAL_CLIENT_MESSAGE }); + } + + let payload; + + try { + // Use MODERATION_BOT_TOKEN for validation + payload = validateAndParseInitData(initDataRaw, config.moderationBotToken); + } catch (error) { + logSecurityEvent('INVALID_INITDATA', req, { reason: error.message }); + return res.status(401).json({ error: `${error.message}. ${OFFICIAL_CLIENT_MESSAGE}` }); + } + + const telegramUser = payload.user; + + 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(); + } 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(); + } + + if (user.banned) { + return res.status(403).json({ error: 'Пользователь заблокирован' }); + } + + await ensureUserSettings(user); + await touchUserActivity(user); + + req.user = user; + req.telegramUser = telegramUser; + next(); + } catch (error) { + console.error('❌ Ошибка авторизации модерации:', error); + res.status(401).json({ error: `Ошибка авторизации. ${OFFICIAL_CLIENT_MESSAGE}` }); + } +}; + module.exports = { authenticate, + authenticateModeration, requireModerator, requireAdmin, touchUserActivity, diff --git a/backend/routes/modApp.js b/backend/routes/modApp.js index fd925fa..f713459 100644 --- a/backend/routes/modApp.js +++ b/backend/routes/modApp.js @@ -3,7 +3,7 @@ const router = express.Router(); const fs = require('fs'); const path = require('path'); const multer = require('multer'); -const { authenticate } = require('../middleware/auth'); +const { authenticateModeration } = require('../middleware/auth'); const { logSecurityEvent } = require('../middleware/logger'); const User = require('../models/User'); const Post = require('../models/Post'); @@ -68,7 +68,7 @@ const serializeUser = (user) => ({ createdAt: user.createdAt }); -router.post('/auth/verify', authenticate, requireModerationAccess, async (req, res) => { +router.post('/auth/verify', authenticateModeration, requireModerationAccess, async (req, res) => { const admins = await listAdmins(); res.json({ @@ -85,7 +85,7 @@ router.post('/auth/verify', authenticate, requireModerationAccess, async (req, r }); }); -router.get('/users', authenticate, requireModerationAccess, async (req, res) => { +router.get('/users', authenticateModeration, requireModerationAccess, async (req, res) => { const { filter = 'active', page = 1, limit = 50 } = req.query; const pageNum = Math.max(parseInt(page, 10) || 1, 1); const limitNum = Math.min(Math.max(parseInt(limit, 10) || 50, 1), 200); @@ -125,7 +125,7 @@ router.get('/users', authenticate, requireModerationAccess, async (req, res) => }); }); -router.put('/users/:id/ban', authenticate, requireModerationAccess, async (req, res) => { +router.put('/users/:id/ban', authenticateModeration, requireModerationAccess, async (req, res) => { const { banned, days } = req.body; const user = await User.findById(req.params.id); @@ -145,7 +145,7 @@ router.put('/users/:id/ban', authenticate, requireModerationAccess, async (req, res.json({ user: serializeUser(user) }); }); -router.get('/posts', authenticate, requireModerationAccess, async (req, res) => { +router.get('/posts', authenticateModeration, requireModerationAccess, async (req, res) => { const { page = 1, limit = 20, author, tag } = req.query; const pageNum = Math.max(parseInt(page, 10) || 1, 1); const limitNum = Math.min(Math.max(parseInt(limit, 10) || 20, 1), 100); @@ -190,7 +190,7 @@ router.get('/posts', authenticate, requireModerationAccess, async (req, res) => }); }); -router.put('/posts/:id', authenticate, requireModerationAccess, async (req, res) => { +router.put('/posts/:id', authenticateModeration, requireModerationAccess, async (req, res) => { const { content, hashtags, tags, isNSFW } = req.body; const post = await Post.findById(req.params.id); @@ -233,7 +233,7 @@ router.put('/posts/:id', authenticate, requireModerationAccess, async (req, res) }); }); -router.delete('/posts/:id', authenticate, requireModerationAccess, async (req, res) => { +router.delete('/posts/:id', authenticateModeration, requireModerationAccess, async (req, res) => { const post = await Post.findById(req.params.id); if (!post) { return res.status(404).json({ error: 'Пост не найден' }); @@ -254,7 +254,7 @@ router.delete('/posts/:id', authenticate, requireModerationAccess, async (req, r res.json({ success: true }); }); -router.delete('/posts/:id/images/:index', authenticate, requireModerationAccess, async (req, res) => { +router.delete('/posts/:id/images/:index', authenticateModeration, requireModerationAccess, async (req, res) => { const { id, index } = req.params; const idx = parseInt(index, 10); @@ -281,7 +281,7 @@ router.delete('/posts/:id/images/:index', authenticate, requireModerationAccess, res.json({ images: post.images }); }); -router.post('/posts/:id/ban', authenticate, requireModerationAccess, async (req, res) => { +router.post('/posts/:id/ban', authenticateModeration, requireModerationAccess, async (req, res) => { const { id } = req.params; const { days = 7 } = req.body; @@ -298,7 +298,7 @@ router.post('/posts/:id/ban', authenticate, requireModerationAccess, async (req, res.json({ user: serializeUser(post.author) }); }); -router.get('/reports', authenticate, requireModerationAccess, async (req, res) => { +router.get('/reports', authenticateModeration, requireModerationAccess, async (req, res) => { const { page = 1, limit = 30, status = 'pending' } = req.query; const pageNum = Math.max(parseInt(page, 10) || 1, 1); const limitNum = Math.min(Math.max(parseInt(limit, 10) || 30, 1), 100); @@ -345,7 +345,7 @@ router.get('/reports', authenticate, requireModerationAccess, async (req, res) = }); }); -router.put('/reports/:id', authenticate, requireModerationAccess, async (req, res) => { +router.put('/reports/:id', authenticateModeration, requireModerationAccess, async (req, res) => { const { status = 'reviewed' } = req.body; const report = await Report.findById(req.params.id); @@ -362,7 +362,7 @@ router.put('/reports/:id', authenticate, requireModerationAccess, async (req, re router.post( '/channel/publish', - authenticate, + authenticateModeration, requireModerationAccess, upload.array('images', 10), async (req, res) => { diff --git a/backend/utils/telegram.js b/backend/utils/telegram.js index 2f3da9d..637be12 100644 --- a/backend/utils/telegram.js +++ b/backend/utils/telegram.js @@ -42,16 +42,19 @@ function manualValidateInitData(initDataRaw, botToken) { return signature === hash; } -function validateAndParseInitData(initDataRaw) { +function validateAndParseInitData(initDataRaw, botToken = null) { + const tokenToUse = botToken || config.telegramBotToken; + console.log('[Telegram] validateAndParseInitData called:', { hasInitData: !!initDataRaw, type: typeof initDataRaw, length: initDataRaw?.length || 0, - preview: initDataRaw?.substring(0, 100) + '...' + preview: initDataRaw?.substring(0, 100) + '...', + usingModerationToken: !!botToken }); - if (!config.telegramBotToken) { - throw new Error('TELEGRAM_BOT_TOKEN не настроен'); + if (!tokenToUse) { + throw new Error('Bot token не настроен'); } if (!initDataRaw || typeof initDataRaw !== 'string') { @@ -69,7 +72,7 @@ function validateAndParseInitData(initDataRaw) { // Try library validation first let valid = false; try { - validate(trimmed, config.telegramBotToken); + validate(trimmed, tokenToUse); valid = true; console.log('[Telegram] Library validation successful'); } catch (libError) { @@ -77,7 +80,7 @@ function validateAndParseInitData(initDataRaw) { // Fallback to manual validation with base64 padding fix try { - valid = manualValidateInitData(trimmed, config.telegramBotToken); + valid = manualValidateInitData(trimmed, tokenToUse); if (valid) { console.log('[Telegram] Manual validation successful'); } @@ -98,16 +101,19 @@ function validateAndParseInitData(initDataRaw) { console.log('[Telegram] Parsed payload:', { hasUser: !!payload?.user, userId: payload?.user?.id, - authDate: payload?.auth_date + authDate: payload?.auth_date, + allKeys: Object.keys(payload) }); if (!payload || !payload.user) { throw new Error('Отсутствует пользователь в initData'); } - const authDate = Number(payload.auth_date); + // Check for auth_date - it might be authDate in parsed payload + const authDate = Number(payload.auth_date || payload.authDate); if (!authDate) { + console.error('[Telegram] Missing auth_date in payload:', payload); throw new Error('Отсутствует auth_date в initData'); }