From 1d4567411bd0b6d4bbfcf52a63cf5f8283d68c4f Mon Sep 17 00:00:00 2001 From: Professional Date: Mon, 26 May 2025 20:01:25 +0700 Subject: [PATCH] =?UTF-8?q?=D1=84=D0=B8=D0=BA=D1=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/controllers/adminController.js | 54 ++++++++------- backend/controllers/authController.js | 77 +++++++++------------ backend/middleware/authMiddleware.js | 13 +++- backend/models/User.js | 17 +++++ src/auth.js | 92 ++++++-------------------- 5 files changed, 112 insertions(+), 141 deletions(-) diff --git a/backend/controllers/adminController.js b/backend/controllers/adminController.js index 17f5c9c..75b5943 100644 --- a/backend/controllers/adminController.js +++ b/backend/controllers/adminController.js @@ -113,7 +113,7 @@ const getUserDetails = async (req, res) => { const toggleUserActive = async (req, res) => { try { const userId = req.params.id; - const { reason } = req.body; // Добавляем возможность указать причину блокировки + const { reason } = req.body; // Причина блокировки const user = await User.findById(userId); if (!user) { @@ -132,9 +132,22 @@ const toggleUserActive = async (req, res) => { return res.status(500).json({ message: 'Внутренняя ошибка сервера: Socket.IO недоступен' }); } - // Изменяем статус активности на противоположный - const wasActive = user.isActive; - user.isActive = !user.isActive; + // Изменяем статус блокировки на противоположный + const wasBlocked = user.blocked; + user.blocked = !user.blocked; + + if (user.blocked) { + // Устанавливаем данные блокировки + user.blockReason = reason || 'Нарушение правил сервиса'; + user.blockedAt = new Date(); + user.blockedBy = req.user._id; + } else { + // Очищаем данные блокировки + user.blockReason = null; + user.blockedAt = null; + user.blockedBy = null; + } + await user.save(); // Получаем глобальный массив активных пользователей из server.js @@ -142,21 +155,20 @@ const toggleUserActive = async (req, res) => { console.log(`[AdminController] Активные пользователи:`, activeUsers); - // Если пользователь был активным и теперь блокируется - if (wasActive && !user.isActive) { - console.log(`[AdminController] Блокировка пользователя ${userId}. Причина: ${reason || 'Не указана'}`); + // Если пользователь теперь заблокирован + if (!wasBlocked && user.blocked) { + console.log(`[AdminController] Блокировка пользователя ${userId}. Причина: ${user.blockReason}`); // Создаем объект с информацией о блокировке const blockData = { message: 'Ваш аккаунт был заблокирован администратором.', - reason: reason || 'Нарушение правил сервиса', + reason: user.blockReason, timestamp: new Date().toISOString(), blocked: true, userId: user._id.toString() }; // Отправляем глобальное событие блокировки всем клиентам - // Это обеспечит получение уведомления даже если в activeUsers неактуальная информация io.emit('global_user_blocked', { userId: user._id.toString(), timestamp: new Date().toISOString() @@ -180,9 +192,6 @@ const toggleUserActive = async (req, res) => { console.log(`[AdminController] Отправка уведомления о блокировке на сокет: ${userSocket.socketId}`); socket.emit("account_blocked", blockData); notificationSent = true; - - // НЕ отключаем сокет принудительно - пусть пользователь остается подключенным - // для получения уведомлений о разблокировке } else { console.log(`[AdminController] Сокет не найден или не подключен для ID: ${userSocket.socketId}`); } @@ -197,15 +206,12 @@ const toggleUserActive = async (req, res) => { console.log(`[AdminController] Пользователь ${userId} не найден в активных соединениях`); } - // НЕ очищаем записи в списке активных пользователей - оставляем их подключенными - // для возможности получения уведомлений о разблокировке - if (!notificationSent) { console.log(`[AdminController] Уведомление о блокировке не было отправлено напрямую, но было отправлено глобальное уведомление`); } } - // Если пользователь был неактивным и теперь разблокируется - else if (!wasActive && user.isActive) { + // Если пользователь был заблокирован и теперь разблокируется + else if (wasBlocked && !user.blocked) { console.log(`[AdminController] Разблокировка пользователя ${userId}`); // Формируем объект с информацией о разблокировке для отправки через сокет @@ -213,27 +219,26 @@ const toggleUserActive = async (req, res) => { message: 'Ваш аккаунт был разблокирован администратором.', timestamp: new Date().toISOString(), userId: user._id.toString(), - unblockType: 'admin_action', // Указываем тип разблокировки + unblockType: 'admin_action', userName: user.name, unblockData: { - canRestoreSession: true, // Флаг возможности восстановления сессии + canRestoreSession: true, } }; - // Отправляем событие разблокировки всем клиентам (анонимно) + // Отправляем событие разблокировки всем клиентам io.emit('global_user_unblocked', { userId: user._id.toString(), timestamp: new Date().toISOString() }); - // Ищем пользователя среди активных пользователей (на случай, если он онлайн с другим аккаунтом) + // Ищем пользователя среди активных пользователей const userSockets = activeUsers.filter(u => u.userId === userId); let notificationSent = false; if (userSockets.length > 0) { for (const userSocket of userSockets) { try { - // Безопасно получаем сокет и проверяем его существование if (io.sockets && io.sockets.sockets) { const socket = io.sockets.sockets.get(userSocket.socketId); @@ -261,8 +266,9 @@ const toggleUserActive = async (req, res) => { } res.json({ - message: user.isActive ? 'Пользователь разблокирован' : 'Пользователь заблокирован', - isActive: user.isActive + message: user.blocked ? 'Пользователь заблокирован' : 'Пользователь разблокирован', + blocked: user.blocked, + blockReason: user.blockReason }); } catch (error) { console.error('Ошибка при изменении статуса пользователя:', error); diff --git a/backend/controllers/authController.js b/backend/controllers/authController.js index e478bb2..d98aaa1 100644 --- a/backend/controllers/authController.js +++ b/backend/controllers/authController.js @@ -110,7 +110,9 @@ const loginUser = async (req, res, next) => { if (!email || !password) { res.status(400); throw new Error('Пожалуйста, укажите email и пароль.'); - } // Проверяем наличие специального логина для администратора + } + + // Проверяем наличие специального логина для администратора if (email === 'admin') { // Для логина "admin" проверяем аккаунт admin@example.com console.log('Попытка входа с административным логином через "admin"'); @@ -183,7 +185,9 @@ const loginUser = async (req, res, next) => { throw new Error('Неверный логин или пароль.'); } return; - } // Перед проверкой обычных пользователей, проверим наличие админа с email + } + + // Перед проверкой обычных пользователей, проверим наличие админа с email if (email === 'admin@example.com') { console.log('Попытка входа с email администратора: admin@example.com'); // Проверяем наличие админа с email admin@example.com @@ -232,57 +236,38 @@ const loginUser = async (req, res, next) => { const isMatch = await user.matchPassword(password); console.log('Результат проверки пароля:', isMatch ? 'Успешно' : 'Неверный пароль'); - // Проверка если аккаунт неактивен (заблокирован) - if (isMatch && !user.isActive && !user.isAdmin) { - console.log('[AuthController] Пользователь заблокирован, но создаем токен для восстановления сессии'); - - // Создаем токен для последующего восстановления сессии после разблокировки - const restorationToken = generateToken(user._id); - - // Обновляем дату последнего входа даже для заблокированного пользователя - user.lastSeen = new Date(); - await user.save(); - - res.status(403); - const error = new Error('Ваш аккаунт заблокирован. Пожалуйста, обратитесь в поддержку.'); - error.blocked = true; - error.userId = user._id; - error.restorationToken = restorationToken; // Добавляем токен для восстановления - error.userName = user.name; - throw error; - } - - if (isMatch) { - res.status(200).json({ - _id: user._id, - name: user.name, - email: user.email, - isAdmin: user.isAdmin || false, // Добавляем информацию о правах администратора - token: generateToken(user._id), - message: 'Вход выполнен успешно!' - }); - } else { + if (!isMatch) { res.status(401); throw new Error('Неверный email или пароль.'); } + + // Проверяем, заблокирован ли пользователь + if (user.blocked && !user.isAdmin) { + console.log('[AuthController] Пользователь заблокирован'); + res.status(403); + throw new Error(`Ваш аккаунт заблокирован. ${user.blockReason || 'Обратитесь в поддержку.'}`); + } + + // Обновляем дату последнего входа + user.lastSeen = new Date(); + await user.save(); + + res.status(200).json({ + _id: user._id, + name: user.name, + email: user.email, + isAdmin: user.isAdmin || false, + blocked: user.blocked || false, + token: generateToken(user._id), + message: 'Вход выполнен успешно!' + }); + } catch (error) { console.error('Ошибка при входе:', error.message); if (!res.statusCode || res.statusCode < 400) { res.status(401); } - // Специальная обработка для заблокированных аккаунтов - if (error.blocked && error.restorationToken) { - return res.json({ - message: error.message, - blocked: true, - userId: error.userId, - restorationToken: error.restorationToken, - userName: error.userName, - stack: process.env.NODE_ENV === 'production' ? null : error.stack - }); - } - res.json({ message: error.message, stack: process.env.NODE_ENV === 'production' ? null : error.stack }); } }; @@ -307,7 +292,9 @@ const getMe = async (req, res, next) => { photos: req.user.photos, location: req.user.location, preferences: req.user.preferences, - isAdmin: req.user.isAdmin || false, // Добавляем информацию об административных правах + isAdmin: req.user.isAdmin || false, + blocked: req.user.blocked || false, + blockReason: req.user.blockReason, createdAt: req.user.createdAt, updatedAt: req.user.updatedAt, }); diff --git a/backend/middleware/authMiddleware.js b/backend/middleware/authMiddleware.js index 26ffd1b..eeff9af 100644 --- a/backend/middleware/authMiddleware.js +++ b/backend/middleware/authMiddleware.js @@ -47,12 +47,23 @@ const protect = async (req, res, next) => { return next(err); } + // Проверяем, не заблокирован ли пользователь + if (req.user.blocked && !req.user.isAdmin) { + console.log('[DEBUG] protect middleware - User is blocked'); + const err = new Error('Ваш аккаунт заблокирован администратором.'); + err.statusCode = 403; + err.blocked = true; + err.blockReason = req.user.blockReason; + return next(err); + } + // Добавляем отладочное логирование для проверки прав администратора console.log('[DEBUG] protect middleware - User loaded:', { id: req.user._id, name: req.user.name, email: req.user.email, - isAdmin: req.user.isAdmin + isAdmin: req.user.isAdmin, + blocked: req.user.blocked }); console.log('[DEBUG] protect middleware - User authenticated, calling next()'); diff --git a/backend/models/User.js b/backend/models/User.js index a1f3913..a7cfd67 100644 --- a/backend/models/User.js +++ b/backend/models/User.js @@ -69,6 +69,23 @@ const userSchema = new mongoose.Schema( type: Boolean, default: false, }, + blocked: { + type: Boolean, + default: false, + }, + blockReason: { + type: String, + default: null, + }, + blockedAt: { + type: Date, + default: null, + }, + blockedBy: { + type: mongoose.Schema.Types.ObjectId, + ref: 'User', + default: null, + }, lastSeen: { type: Date, default: Date.now, diff --git a/src/auth.js b/src/auth.js index ecf8f7d..06fa21f 100644 --- a/src/auth.js +++ b/src/auth.js @@ -24,38 +24,6 @@ function setUserData(userData, userToken) { // Функция для загрузки данных пользователя, если токен есть (например, при обновлении страницы) async function fetchUser() { if (token.value) { - // Проверяем, есть ли информация о блокировке в localStorage - const blockedInfoStr = localStorage.getItem('accountBlockedInfo'); - if (blockedInfoStr) { - try { - const blockedInfo = JSON.parse(blockedInfoStr); - if (blockedInfo.blocked) { - console.log('[Auth] Обнаружена информация о блокировке в localStorage, выполняем выход'); - - // Показываем уведомление о блокировке - const notificationEvent = new CustomEvent('show-toast', { - detail: { - message: blockedInfo.message || 'Ваш аккаунт заблокирован администратором.', - type: 'error', - duration: 10000 - } - }); - window.dispatchEvent(notificationEvent); - - // Выходим из системы - await logout(); - - // Перенаправляем на страницу входа с параметром блокировки - router.replace({ path: '/login', query: { blocked: 'true' } }); - return; - } - } catch (error) { - console.error('[Auth] Ошибка при парсинге информации о блокировке:', error); - // Удаляем поврежденные данные - localStorage.removeItem('accountBlockedInfo'); - } - } - try { console.log('Пытаемся загрузить пользователя с токеном из localStorage...'); const response = await api.getMe(); // Используем наш API сервис @@ -70,46 +38,27 @@ async function fetchUser() { // Добавляем дополнительное логирование для отладки console.log('Получены данные пользователя:', response.data); console.log('Значение isAdmin в ответе:', response.data.isAdmin); + console.log('Статус блокировки:', response.data.blocked); // Проверяем, не заблокирован ли пользователь if (response.data.blocked) { console.log('[Auth] Пользователь заблокирован, выполняем принудительный выход'); - // Сохраняем информацию о блокировке - const blockedInfo = { - blocked: true, - message: 'Ваш аккаунт заблокирован администратором.', - reason: response.data.blockReason || 'Нарушение правил сервиса', - timestamp: new Date().toISOString() - }; - localStorage.setItem('accountBlockedInfo', JSON.stringify(blockedInfo)); + // Показываем уведомление о блокировке + const notificationEvent = new CustomEvent('show-toast', { + detail: { + message: `Ваш аккаунт заблокирован. ${response.data.blockReason || 'Обратитесь в поддержку.'}`, + type: 'error', + duration: 10000 + } + }); + window.dispatchEvent(notificationEvent); // Выходим из системы await logout(); return; } - // Проверяем, была ли разблокировка (если есть информация о предыдущей блокировке) - const blockedInfoStr = localStorage.getItem('accountBlockedInfo'); - if (blockedInfoStr) { - console.log('[Auth] Обнаружена предыдущая блокировка, пользователь теперь разблокирован'); - - // Удаляем информацию о блокировке - localStorage.removeItem('accountBlockedInfo'); - - // Показываем уведомление о разблокировке - setTimeout(() => { - const notificationEvent = new CustomEvent('show-toast', { - detail: { - message: 'Ваш аккаунт был разблокирован. Добро пожаловать обратно!', - type: 'success', - duration: 5000 - } - }); - window.dispatchEvent(notificationEvent); - }, 1000); - } - // Сохраняем данные пользователя user.value = response.data; console.log('Пользователь успешно загружен:', user.value); @@ -119,20 +68,21 @@ async function fetchUser() { console.error('Не удалось загрузить пользователя по токену:', error.response ? error.response.data : error.message); // Проверяем, не связана ли ошибка с блокировкой - if (error.response && error.response.status === 403 && error.response.data.message && error.response.data.message.includes('заблокирован')) { + if (error.response && error.response.status === 403) { console.log('[Auth] Пользователь заблокирован согласно ответу сервера'); - // Сохраняем информацию о блокировке - const blockedInfo = { - blocked: true, - message: error.response.data.message || 'Ваш аккаунт заблокирован администратором.', - reason: error.response.data.reason || 'Нарушение правил сервиса', - timestamp: new Date().toISOString() - }; - localStorage.setItem('accountBlockedInfo', JSON.stringify(blockedInfo)); + // Показываем уведомление о блокировке + const notificationEvent = new CustomEvent('show-toast', { + detail: { + message: error.response.data.message || 'Ваш аккаунт заблокирован администратором.', + type: 'error', + duration: 10000 + } + }); + window.dispatchEvent(notificationEvent); } - // Если токен невалиден, очищаем его и данные пользователя + // Если токен невалиден или пользователь заблокирован, очищаем его и данные пользователя await logout(); // Вызываем logout, чтобы очистить всё } } else {