Update files

This commit is contained in:
glpshchn 2025-11-11 02:11:33 +03:00
parent b6036af3f1
commit c3f2746723
3 changed files with 105 additions and 20 deletions

View File

@ -142,8 +142,87 @@ const requireAdmin = (req, res, next) => {
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 = { module.exports = {
authenticate, authenticate,
authenticateModeration,
requireModerator, requireModerator,
requireAdmin, requireAdmin,
touchUserActivity, touchUserActivity,

View File

@ -3,7 +3,7 @@ const router = express.Router();
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const multer = require('multer'); const multer = require('multer');
const { authenticate } = require('../middleware/auth'); const { authenticateModeration } = require('../middleware/auth');
const { logSecurityEvent } = require('../middleware/logger'); const { logSecurityEvent } = require('../middleware/logger');
const User = require('../models/User'); const User = require('../models/User');
const Post = require('../models/Post'); const Post = require('../models/Post');
@ -68,7 +68,7 @@ const serializeUser = (user) => ({
createdAt: user.createdAt 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(); const admins = await listAdmins();
res.json({ 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 { filter = 'active', page = 1, limit = 50 } = req.query;
const pageNum = Math.max(parseInt(page, 10) || 1, 1); const pageNum = Math.max(parseInt(page, 10) || 1, 1);
const limitNum = Math.min(Math.max(parseInt(limit, 10) || 50, 1), 200); 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 { banned, days } = req.body;
const user = await User.findById(req.params.id); 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) }); 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 { page = 1, limit = 20, author, tag } = req.query;
const pageNum = Math.max(parseInt(page, 10) || 1, 1); const pageNum = Math.max(parseInt(page, 10) || 1, 1);
const limitNum = Math.min(Math.max(parseInt(limit, 10) || 20, 1), 100); 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 { content, hashtags, tags, isNSFW } = req.body;
const post = await Post.findById(req.params.id); 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); const post = await Post.findById(req.params.id);
if (!post) { if (!post) {
return res.status(404).json({ error: 'Пост не найден' }); return res.status(404).json({ error: 'Пост не найден' });
@ -254,7 +254,7 @@ router.delete('/posts/:id', authenticate, requireModerationAccess, async (req, r
res.json({ success: true }); 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 { id, index } = req.params;
const idx = parseInt(index, 10); const idx = parseInt(index, 10);
@ -281,7 +281,7 @@ router.delete('/posts/:id/images/:index', authenticate, requireModerationAccess,
res.json({ images: post.images }); 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 { id } = req.params;
const { days = 7 } = req.body; const { days = 7 } = req.body;
@ -298,7 +298,7 @@ router.post('/posts/:id/ban', authenticate, requireModerationAccess, async (req,
res.json({ user: serializeUser(post.author) }); 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 { page = 1, limit = 30, status = 'pending' } = req.query;
const pageNum = Math.max(parseInt(page, 10) || 1, 1); const pageNum = Math.max(parseInt(page, 10) || 1, 1);
const limitNum = Math.min(Math.max(parseInt(limit, 10) || 30, 1), 100); 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 { status = 'reviewed' } = req.body;
const report = await Report.findById(req.params.id); const report = await Report.findById(req.params.id);
@ -362,7 +362,7 @@ router.put('/reports/:id', authenticate, requireModerationAccess, async (req, re
router.post( router.post(
'/channel/publish', '/channel/publish',
authenticate, authenticateModeration,
requireModerationAccess, requireModerationAccess,
upload.array('images', 10), upload.array('images', 10),
async (req, res) => { async (req, res) => {

View File

@ -42,16 +42,19 @@ function manualValidateInitData(initDataRaw, botToken) {
return signature === hash; return signature === hash;
} }
function validateAndParseInitData(initDataRaw) { function validateAndParseInitData(initDataRaw, botToken = null) {
const tokenToUse = botToken || config.telegramBotToken;
console.log('[Telegram] validateAndParseInitData called:', { console.log('[Telegram] validateAndParseInitData called:', {
hasInitData: !!initDataRaw, hasInitData: !!initDataRaw,
type: typeof initDataRaw, type: typeof initDataRaw,
length: initDataRaw?.length || 0, length: initDataRaw?.length || 0,
preview: initDataRaw?.substring(0, 100) + '...' preview: initDataRaw?.substring(0, 100) + '...',
usingModerationToken: !!botToken
}); });
if (!config.telegramBotToken) { if (!tokenToUse) {
throw new Error('TELEGRAM_BOT_TOKEN не настроен'); throw new Error('Bot token не настроен');
} }
if (!initDataRaw || typeof initDataRaw !== 'string') { if (!initDataRaw || typeof initDataRaw !== 'string') {
@ -69,7 +72,7 @@ function validateAndParseInitData(initDataRaw) {
// Try library validation first // Try library validation first
let valid = false; let valid = false;
try { try {
validate(trimmed, config.telegramBotToken); validate(trimmed, tokenToUse);
valid = true; valid = true;
console.log('[Telegram] Library validation successful'); console.log('[Telegram] Library validation successful');
} catch (libError) { } catch (libError) {
@ -77,7 +80,7 @@ function validateAndParseInitData(initDataRaw) {
// Fallback to manual validation with base64 padding fix // Fallback to manual validation with base64 padding fix
try { try {
valid = manualValidateInitData(trimmed, config.telegramBotToken); valid = manualValidateInitData(trimmed, tokenToUse);
if (valid) { if (valid) {
console.log('[Telegram] Manual validation successful'); console.log('[Telegram] Manual validation successful');
} }
@ -98,16 +101,19 @@ function validateAndParseInitData(initDataRaw) {
console.log('[Telegram] Parsed payload:', { console.log('[Telegram] Parsed payload:', {
hasUser: !!payload?.user, hasUser: !!payload?.user,
userId: payload?.user?.id, userId: payload?.user?.id,
authDate: payload?.auth_date authDate: payload?.auth_date,
allKeys: Object.keys(payload)
}); });
if (!payload || !payload.user) { if (!payload || !payload.user) {
throw new Error('Отсутствует пользователь в initData'); 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) { if (!authDate) {
console.error('[Telegram] Missing auth_date in payload:', payload);
throw new Error('Отсутствует auth_date в initData'); throw new Error('Отсутствует auth_date в initData');
} }