nakama/backend/middleware/auth.js

183 lines
6.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const crypto = require('crypto');
const User = require('../models/User');
const { validateTelegramId } = require('./validator');
const { logSecurityEvent } = require('./logger');
const config = require('../config');
// Проверка 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'];
const telegramUserId = req.headers['x-telegram-user-id'];
// Если нет initData, но есть telegramUserId (сохраненная OAuth сессия)
if (!initData && telegramUserId) {
try {
// Найти пользователя по telegramId
const user = await User.findOne({ telegramId: telegramUserId.toString() });
if (!user) {
return res.status(401).json({ error: 'Пользователь не найден' });
}
if (user.banned) {
return res.status(403).json({ error: 'Пользователь заблокирован' });
}
req.user = user;
req.telegramUser = { id: user.telegramId };
return next();
} catch (error) {
console.error('❌ Ошибка авторизации по сохраненной сессии:', error);
return res.status(401).json({ error: 'Ошибка авторизации' });
}
}
if (!initData) {
console.warn('⚠️ Нет x-telegram-init-data заголовка');
return res.status(401).json({ error: 'Не авторизован' });
}
// Получаем user из initData
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: 'Неверный формат данных авторизации' });
}
}
const userParam = urlParams.get('user');
if (!userParam) {
console.warn('⚠️ Нет user параметра в initData');
return res.status(401).json({ error: 'Данные пользователя не найдены' });
}
let telegramUser;
try {
telegramUser = JSON.parse(userParam);
} catch (e) {
console.error('❌ Ошибка парсинга user:', e.message);
return res.status(401).json({ error: 'Ошибка парсинга данных пользователя' });
}
req.telegramUser = telegramUser;
// Валидация 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);
if (!isValid) {
logSecurityEvent('INVALID_TELEGRAM_SIGNATURE', req, {
telegramId: telegramUser.id,
hasToken: !!config.telegramBotToken
});
// В production строгая проверка
if (config.isProduction()) {
return res.status(401).json({ error: 'Неверные данные авторизации' });
}
}
} else if (config.isProduction()) {
logSecurityEvent('MISSING_BOT_TOKEN', req);
console.warn('⚠️ TELEGRAM_BOT_TOKEN не установлен, проверка подписи пропущена');
}
// Найти или создать пользователя
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();
console.log(`✅ Создан новый пользователь: ${user.username}`);
}
req.user = user;
next();
} catch (error) {
console.error('❌ Ошибка авторизации:', error);
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
};