nakama/backend/middleware/auth.js

155 lines
4.6 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 User = require('../models/User');
const { validateTelegramId } = require('./validator');
const { logSecurityEvent } = require('./logger');
const config = require('../config');
const {
ACCESS_COOKIE,
REFRESH_COOKIE,
signAuthTokens,
setAuthCookies,
clearAuthCookies,
verifyAccessToken,
verifyRefreshToken
} = require('../utils/tokens');
const OFFICIAL_CLIENT_MESSAGE = 'Используйте официальный клиент. Сообщите об ошибке в https://t.me/NakamaReportbot';
const ALLOWED_SEARCH_PREFERENCES = ['furry', 'anime'];
const touchUserActivity = async (user) => {
if (!user) return;
const now = Date.now();
const shouldUpdate =
!user.lastActiveAt ||
Math.abs(now - new Date(user.lastActiveAt).getTime()) > 5 * 60 * 1000;
if (shouldUpdate) {
user.lastActiveAt = new Date(now);
await user.save();
}
};
const ensureUserSettings = async (user) => {
if (!user) return;
let updated = false;
if (!user.settings) {
user.settings = {};
updated = true;
}
if (!ALLOWED_SEARCH_PREFERENCES.includes(user.settings.searchPreference)) {
user.settings.searchPreference = 'furry';
updated = true;
}
if (!user.settings.whitelist) {
user.settings.whitelist = { noNSFW: true };
updated = true;
} else if (user.settings.whitelist.noNSFW === undefined) {
user.settings.whitelist.noNSFW = true;
updated = true;
}
if (updated) {
user.markModified('settings');
await user.save();
}
};
// Middleware для проверки авторизации
const authenticate = async (req, res, next) => {
try {
const accessToken = req.cookies[ACCESS_COOKIE];
const refreshToken = req.cookies[REFRESH_COOKIE];
let tokenPayload = null;
if (accessToken) {
try {
tokenPayload = verifyAccessToken(accessToken);
} catch (error) {
if (error.name !== 'TokenExpiredError') {
logSecurityEvent('INVALID_ACCESS_TOKEN', req, { error: error.message });
clearAuthCookies(res);
return res.status(401).json({ error: OFFICIAL_CLIENT_MESSAGE });
}
}
}
if (!tokenPayload && refreshToken) {
try {
const refreshPayload = verifyRefreshToken(refreshToken);
const userForRefresh = await User.findById(refreshPayload.userId);
if (!userForRefresh) {
clearAuthCookies(res);
return res.status(401).json({ error: OFFICIAL_CLIENT_MESSAGE });
}
const tokens = signAuthTokens(userForRefresh);
setAuthCookies(res, tokens);
tokenPayload = verifyAccessToken(tokens.accessToken);
} catch (error) {
logSecurityEvent('INVALID_REFRESH_TOKEN', req, { error: error.message });
clearAuthCookies(res);
return res.status(401).json({ error: OFFICIAL_CLIENT_MESSAGE });
}
}
if (!tokenPayload) {
logSecurityEvent('AUTH_TOKEN_MISSING', req);
return res.status(401).json({ error: OFFICIAL_CLIENT_MESSAGE });
}
if (!validateTelegramId(tokenPayload.telegramId)) {
logSecurityEvent('INVALID_TELEGRAM_ID', req, { telegramId: tokenPayload.telegramId });
clearAuthCookies(res);
return res.status(401).json({ error: 'Неверный ID пользователя' });
}
let user = await User.findOne({ telegramId: tokenPayload.telegramId.toString() });
if (!user) {
clearAuthCookies(res);
return res.status(401).json({ error: OFFICIAL_CLIENT_MESSAGE });
}
if (user.banned) {
clearAuthCookies(res);
return res.status(403).json({ error: 'Пользователь заблокирован' });
}
await ensureUserSettings(user);
await touchUserActivity(user);
req.user = user;
req.telegramUser = { id: user.telegramId };
next();
} catch (error) {
console.error('❌ Ошибка авторизации:', error);
res.status(401).json({ error: `Ошибка авторизации. ${OFFICIAL_CLIENT_MESSAGE}` });
}
};
// 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,
touchUserActivity,
ensureUserSettings
};