2025-11-03 20:35:01 +00:00
|
|
|
|
const crypto = require('crypto');
|
|
|
|
|
|
const User = require('../models/User');
|
2025-11-04 21:51:05 +00:00
|
|
|
|
const { validateTelegramId } = require('./validator');
|
|
|
|
|
|
const { logSecurityEvent } = require('./logger');
|
|
|
|
|
|
const config = require('../config');
|
2025-11-03 20:35:01 +00:00
|
|
|
|
|
|
|
|
|
|
// Проверка Telegram Init Data
|
|
|
|
|
|
function validateTelegramWebAppData(initData, botToken) {
|
|
|
|
|
|
const urlParams = new URLSearchParams(initData);
|
|
|
|
|
|
const hash = urlParams.get('hash');
|
|
|
|
|
|
urlParams.delete('hash');
|
|
|
|
|
|
|
|
|
|
|
|
const dataCheckString = Array.from(urlParams.entries())
|
|
|
|
|
|
.sort(([a], [b]) => a.localeCompare(b))
|
|
|
|
|
|
.map(([key, value]) => `${key}=${value}`)
|
|
|
|
|
|
.join('\n');
|
|
|
|
|
|
|
|
|
|
|
|
const secretKey = crypto
|
|
|
|
|
|
.createHmac('sha256', 'WebAppData')
|
|
|
|
|
|
.update(botToken)
|
|
|
|
|
|
.digest();
|
|
|
|
|
|
|
|
|
|
|
|
const calculatedHash = crypto
|
|
|
|
|
|
.createHmac('sha256', secretKey)
|
|
|
|
|
|
.update(dataCheckString)
|
|
|
|
|
|
.digest('hex');
|
|
|
|
|
|
|
|
|
|
|
|
return calculatedHash === hash;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Middleware для проверки авторизации
|
|
|
|
|
|
const authenticate = async (req, res, next) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const initData = req.headers['x-telegram-init-data'];
|
|
|
|
|
|
|
|
|
|
|
|
if (!initData) {
|
2025-11-03 22:41:34 +00:00
|
|
|
|
console.warn('⚠️ Нет x-telegram-init-data заголовка');
|
2025-11-03 20:35:01 +00:00
|
|
|
|
return res.status(401).json({ error: 'Не авторизован' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-03 21:29:00 +00:00
|
|
|
|
// Получаем user из initData
|
2025-11-03 22:41:34 +00:00
|
|
|
|
let urlParams;
|
|
|
|
|
|
try {
|
|
|
|
|
|
urlParams = new URLSearchParams(initData);
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
// Если initData не URLSearchParams, попробуем как JSON
|
|
|
|
|
|
try {
|
|
|
|
|
|
const parsed = JSON.parse(initData);
|
|
|
|
|
|
if (parsed.user) {
|
|
|
|
|
|
req.telegramUser = parsed.user;
|
|
|
|
|
|
// Найти или создать пользователя
|
|
|
|
|
|
let user = await User.findOne({ telegramId: parsed.user.id.toString() });
|
|
|
|
|
|
if (!user) {
|
|
|
|
|
|
user = new User({
|
|
|
|
|
|
telegramId: parsed.user.id.toString(),
|
|
|
|
|
|
username: parsed.user.username || parsed.user.first_name,
|
|
|
|
|
|
firstName: parsed.user.first_name,
|
|
|
|
|
|
lastName: parsed.user.last_name,
|
|
|
|
|
|
photoUrl: parsed.user.photo_url
|
|
|
|
|
|
});
|
|
|
|
|
|
await user.save();
|
|
|
|
|
|
console.log(`✅ Создан новый пользователь: ${user.username}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
req.user = user;
|
|
|
|
|
|
return next();
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e2) {
|
|
|
|
|
|
console.error('❌ Ошибка парсинга initData:', e2.message);
|
|
|
|
|
|
return res.status(401).json({ error: 'Неверный формат данных авторизации' });
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-03 21:29:00 +00:00
|
|
|
|
const userParam = urlParams.get('user');
|
2025-11-03 20:35:01 +00:00
|
|
|
|
|
2025-11-03 21:29:00 +00:00
|
|
|
|
if (!userParam) {
|
2025-11-03 22:41:34 +00:00
|
|
|
|
console.warn('⚠️ Нет user параметра в initData');
|
2025-11-03 21:29:00 +00:00
|
|
|
|
return res.status(401).json({ error: 'Данные пользователя не найдены' });
|
2025-11-03 20:35:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-03 22:41:34 +00:00
|
|
|
|
let telegramUser;
|
|
|
|
|
|
try {
|
|
|
|
|
|
telegramUser = JSON.parse(userParam);
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.error('❌ Ошибка парсинга user:', e.message);
|
|
|
|
|
|
return res.status(401).json({ error: 'Ошибка парсинга данных пользователя' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-03 20:35:01 +00:00
|
|
|
|
req.telegramUser = telegramUser;
|
|
|
|
|
|
|
2025-11-04 21:51:05 +00:00
|
|
|
|
// Валидация Telegram ID
|
|
|
|
|
|
if (!validateTelegramId(telegramUser.id)) {
|
|
|
|
|
|
logSecurityEvent('INVALID_TELEGRAM_ID', req, { telegramId: telegramUser.id });
|
|
|
|
|
|
return res.status(401).json({ error: 'Неверный ID пользователя' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Проверка подписи Telegram (строгая проверка в production)
|
|
|
|
|
|
if (config.telegramBotToken) {
|
|
|
|
|
|
const isValid = validateTelegramWebAppData(initData, config.telegramBotToken);
|
2025-11-03 21:29:00 +00:00
|
|
|
|
|
|
|
|
|
|
if (!isValid) {
|
2025-11-04 21:51:05 +00:00
|
|
|
|
logSecurityEvent('INVALID_TELEGRAM_SIGNATURE', req, {
|
|
|
|
|
|
telegramId: telegramUser.id,
|
|
|
|
|
|
hasToken: !!config.telegramBotToken
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// В production строгая проверка
|
|
|
|
|
|
if (config.isProduction()) {
|
|
|
|
|
|
return res.status(401).json({ error: 'Неверные данные авторизации' });
|
|
|
|
|
|
}
|
2025-11-03 21:29:00 +00:00
|
|
|
|
}
|
2025-11-04 21:51:05 +00:00
|
|
|
|
} else if (config.isProduction()) {
|
|
|
|
|
|
logSecurityEvent('MISSING_BOT_TOKEN', req);
|
2025-11-03 21:29:00 +00:00
|
|
|
|
console.warn('⚠️ TELEGRAM_BOT_TOKEN не установлен, проверка подписи пропущена');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-03 20:35:01 +00:00
|
|
|
|
// Найти или создать пользователя
|
|
|
|
|
|
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();
|
2025-11-03 21:29:00 +00:00
|
|
|
|
console.log(`✅ Создан новый пользователь: ${user.username}`);
|
2025-11-03 20:35:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
req.user = user;
|
|
|
|
|
|
next();
|
|
|
|
|
|
} catch (error) {
|
2025-11-03 21:29:00 +00:00
|
|
|
|
console.error('❌ Ошибка авторизации:', error);
|
2025-11-03 20:35:01 +00:00
|
|
|
|
res.status(401).json({ error: 'Ошибка авторизации' });
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Middleware для проверки роли модератора
|
|
|
|
|
|
const requireModerator = (req, res, next) => {
|
|
|
|
|
|
if (req.user.role !== 'moderator' && req.user.role !== 'admin') {
|
|
|
|
|
|
return res.status(403).json({ error: 'Требуются права модератора' });
|
|
|
|
|
|
}
|
|
|
|
|
|
next();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Middleware для проверки роли админа
|
|
|
|
|
|
const requireAdmin = (req, res, next) => {
|
|
|
|
|
|
if (req.user.role !== 'admin') {
|
|
|
|
|
|
return res.status(403).json({ error: 'Требуются права администратора' });
|
|
|
|
|
|
}
|
|
|
|
|
|
next();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
|
|
authenticate,
|
|
|
|
|
|
requireModerator,
|
|
|
|
|
|
requireAdmin
|
|
|
|
|
|
};
|