Update files
This commit is contained in:
parent
b6036af3f1
commit
c3f2746723
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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) => {
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue