const db = require('../db'); const logger = require('../utils/logger'); const { ethers } = require('ethers'); const crypto = require('crypto'); const { processMessage } = require('./ai-assistant'); // Используем AI Assistant const verificationService = require('./verification-service'); // Используем сервис верификации const identityService = require('./identity-service'); // <-- ДОБАВЛЕН ИМПОРТ const ADMIN_CONTRACTS = [ { address: '0xd95a45fc46a7300e6022885afec3d618d7d3f27c', network: 'eth' }, { address: '0x4B294265720B09ca39BFBA18c7E368413c0f68eB', network: 'bsc' }, { address: '0xdce769b847a0a697239777d0b1c7dd33b6012ba0', network: 'arbitrum' }, { address: '0x351f59de4fedbdf7601f5592b93db3b9330c1c1d', network: 'polygon' }, ]; const ERC20_ABI = ['function balanceOf(address owner) view returns (uint256)']; class AuthService { constructor() { this.providers = { eth: new ethers.JsonRpcProvider(process.env.RPC_URL_ETH), polygon: new ethers.JsonRpcProvider(process.env.RPC_URL_POLYGON), bsc: new ethers.JsonRpcProvider(process.env.RPC_URL_BSC), arbitrum: new ethers.JsonRpcProvider(process.env.RPC_URL_ARBITRUM), }; } // Проверка подписи async verifySignature(message, signature, address) { try { if (!message || !signature || !address) return false; // Нормализуем входящий адрес const normalizedAddress = ethers.getAddress(address).toLowerCase(); // Восстанавливаем адрес из подписи const recoveredAddress = ethers.verifyMessage(message, signature); // Сравниваем нормализованные адреса return ethers.getAddress(recoveredAddress).toLowerCase() === normalizedAddress; } catch (error) { logger.error('Error in signature verification:', error); return false; } } /** * Находит или создает пользователя по адресу кошелька * @param {string} address - Адрес кошелька * @returns {Promise<{userId: number, isAdmin: boolean}>} */ async findOrCreateUser(address) { try { // Нормализуем адрес - всегда приводим к нижнему регистру const normalizedAddress = ethers.getAddress(address).toLowerCase(); // Ищем пользователя по адресу в таблице user_identities const userResult = await db.query( ` SELECT u.* FROM users u JOIN user_identities ui ON u.id = ui.user_id WHERE ui.provider = 'wallet' AND ui.provider_id = $1 `, [normalizedAddress] ); if (userResult.rows.length > 0) { const user = userResult.rows[0]; // Проверяем роль администратора при каждой аутентификации const isAdmin = await this.checkAdminRole(normalizedAddress); // Если статус админа изменился, обновляем роль в базе данных if (user.role === 'admin' && !isAdmin) { await db.query('UPDATE users SET role = $1 WHERE id = $2', ['user', user.id]); logger.info(`Updated user ${user.id} role to user (admin tokens no longer present)`); return { userId: user.id, isAdmin: false }; } else if (user.role !== 'admin' && isAdmin) { await db.query('UPDATE users SET role = $1 WHERE id = $2', ['admin', user.id]); logger.info(`Updated user ${user.id} role to admin (admin tokens found)`); return { userId: user.id, isAdmin: true }; } return { userId: user.id, isAdmin: user.role === 'admin', }; } // Если пользователь не найден, создаем нового const newUserResult = await db.query('INSERT INTO users (role) VALUES ($1) RETURNING id', [ 'user', ]); const userId = newUserResult.rows[0].id; // Добавляем идентификатор кошелька (всегда в нижнем регистре) await db.query( 'INSERT INTO user_identities (user_id, provider, provider_id) VALUES ($1, $2, $3)', [userId, 'wallet', normalizedAddress] ); // Проверяем, есть ли у пользователя роль админа const isAdmin = await this.checkAdminRole(normalizedAddress); logger.info(`New user ${userId} role check result: ${isAdmin ? 'admin' : 'user'}`); // Если у пользователя есть админские токены, обновляем его роль if (isAdmin) { await db.query('UPDATE users SET role = $1 WHERE id = $2', ['admin', userId]); logger.info( `New user ${userId} with wallet ${normalizedAddress} automatically granted admin role` ); } return { userId, isAdmin }; } catch (error) { logger.error('Error finding or creating user:', error); throw error; } } /** * Основной метод проверки роли админа * @param {string} address - Адрес кошелька * @returns {Promise} - Является ли пользователь админом */ async checkAdminRole(address) { if (!address) return false; logger.info(`Checking admin role for address: ${address}`); let foundTokens = false; let errorCount = 0; const balances = {}; const totalNetworks = ADMIN_CONTRACTS.length; // Создаем массив промисов для параллельной проверки балансов const checkPromises = ADMIN_CONTRACTS.map(async (contract) => { try { const provider = this.providers[contract.network]; if (!provider) { logger.error(`No provider available for network ${contract.network}`); balances[contract.network] = 'Error: No provider'; errorCount++; return null; } // Проверяем доступность провайдера try { // Проверка доступности сети с таймаутом const networkCheckPromise = provider.getNetwork(); const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Network check timeout')), 3000) ); await Promise.race([networkCheckPromise, timeoutPromise]); } catch (networkError) { logger.error( `Provider for ${contract.network} is not available: ${networkError.message}` ); balances[contract.network] = 'Error: Network unavailable'; errorCount++; return null; } const tokenContract = new ethers.Contract(contract.address, ERC20_ABI, provider); // Создаем промис с таймаутом const balancePromise = tokenContract.balanceOf(address); const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 3000) ); // Ждем первый выполненный промис const balance = await Promise.race([balancePromise, timeoutPromise]); const formattedBalance = ethers.formatUnits(balance, 18); balances[contract.network] = formattedBalance; logger.info(`Token balance on ${contract.network}:`, { address, contract: contract.address, balance: formattedBalance, hasTokens: balance > 0, }); if (parseFloat(formattedBalance) > 0) { logger.info(`Found admin tokens on ${contract.network}`); foundTokens = true; } return { network: contract.network, balance: formattedBalance }; } catch (error) { logger.error(`Error checking balance in ${contract.network}:`, { address, contract: contract.address, error: error.message || 'Unknown error', }); balances[contract.network] = 'Error'; errorCount++; return null; } }); // Ждем выполнения всех проверок await Promise.all(checkPromises); // Если все запросы завершились с ошибкой, считаем, что проверка не удалась if (errorCount === totalNetworks) { logger.error(`All network checks for ${address} failed. Cannot verify admin status.`); return false; } if (foundTokens) { logger.info(`Admin role summary for ${address}:`, { networks: Object.keys(balances).filter( (net) => balances[net] > 0 && balances[net] !== 'Error' ), balances, }); logger.info(`Admin role granted for ${address}`); return true; } logger.info(`Admin role denied - no tokens found for ${address}`); return false; } /** * Получение балансов токенов для адреса * @param {string} address - Адрес кошелька * @returns {Promise} - Объект с балансами токенов */ async getTokenBalances(address) { if (!address) { logger.error('No address provided for getTokenBalances'); return { eth: '0', bsc: '0', arbitrum: '0', polygon: '0', }; } const balances = {}; const timeout = 3000; // 3 секунды таймаут for (const contract of ADMIN_CONTRACTS) { try { const provider = this.providers[contract.network]; if (!provider) { logger.error(`No provider for network ${contract.network}`); balances[contract.network] = '0'; continue; } // Проверяем доступность провайдера try { // Проверка доступности сети с таймаутом const networkCheckPromise = provider.getNetwork(); const networkTimeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Network check timeout')), timeout) ); await Promise.race([networkCheckPromise, networkTimeoutPromise]); } catch (networkError) { logger.error( `Provider for ${contract.network} is not available: ${networkError.message}` ); balances[contract.network] = '0'; continue; } const tokenContract = new ethers.Contract(contract.address, ERC20_ABI, provider); // Создаем промис с таймаутом const balancePromise = tokenContract.balanceOf(address); const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), timeout) ); // Ждем первый выполненный промис const balance = await Promise.race([balancePromise, timeoutPromise]); const formattedBalance = ethers.formatUnits(balance, 18); logger.info(`Token balance for ${address} on ${contract.network}:`, { contract: contract.address, balance: formattedBalance, timestamp: new Date().toISOString(), }); balances[contract.network] = formattedBalance; } catch (error) { logger.error(`Error getting balance for ${contract.network}:`, { address, contract: contract.address, error: error.message || 'Unknown error', timestamp: new Date().toISOString(), }); balances[contract.network] = '0'; } } logger.info(`Token balances fetched for ${address}:`, { ...balances, timestamp: new Date().toISOString(), }); return balances; } // Создание сессии с проверкой роли async createSession(session, { userId, authenticated, authType, guestId, address }) { try { // Если пользователь аутентифицирован, обрабатываем гостевые сообщения if (authenticated && guestId) { await this.processAndCleanupGuestData(userId, guestId, session); } // Обновляем данные сессии session.userId = userId; session.authenticated = authenticated; session.authType = authType; // Сохраняем адрес кошелька если есть if (address) { session.address = address; } // Сохраняем сессию в БД const result = await db.query( `UPDATE session SET sess = $1 WHERE sid = $2`, [ JSON.stringify({ userId, authenticated, authType, address, cookie: session.cookie, }), session.id, ] ); return true; } catch (error) { logger.error('Error creating session:', error); return false; } } /** * Обработка и очистка гостевых данных после авторизации * @param {number} userId - ID пользователя * @param {string} guestId - Гостевой ID * @param {Object} session - Объект сессии */ async processAndCleanupGuestData(userId, guestId, session) { try { // Обрабатываем гостевые сообщения const { processGuestMessages } = require('../routes/chat'); await processGuestMessages(userId, guestId); // Очищаем гостевой ID из сессии delete session.guestId; if (session.previousGuestId) { delete session.previousGuestId; } logger.info(`Cleaned up guest data for user ${userId}, guest ID ${guestId}`); } catch (error) { logger.error('Error processing and cleaning up guest data:', error); throw error; } } async getSession(sessionId) { try { const result = await db.query('SELECT * FROM session WHERE sid = $1', [sessionId]); return result.rows[0]; } catch (error) { console.error('Error getting session:', error); throw error; } } // Получение связанного кошелька async getLinkedWallet(userId) { logger.info(`[getLinkedWallet] Called with userId: ${userId} (Type: ${typeof userId})`); try { const result = await db.query( `SELECT provider_id as address FROM user_identities WHERE user_id = $1 AND provider = 'wallet'`, [userId] ); logger.info(`[getLinkedWallet] DB query result for userId ${userId}:`, result.rows); const address = result.rows[0]?.address; logger.info(`[getLinkedWallet] Returning address: ${address} for userId ${userId}`); return address; } catch (error) { logger.error(`[getLinkedWallet] Error fetching linked wallet for userId ${userId}:`, error); return undefined; } } /** * Проверяет роль пользователя Telegram * @param {number} userId - ID пользователя * @returns {Promise} - Роль пользователя */ async checkUserRole(userId) { try { // Проверяем наличие связанного кошелька const wallet = await this.getLinkedWallet(userId); // Если кошелек не привязан, пользователь получает роль user // с базовым доступом к чату и истории сообщений if (!wallet) { logger.info(`No wallet linked for user ${userId}, assigning basic user role`); return 'user'; } // Если есть кошелек, проверяем админские токены const isAdmin = await this.checkAdminRole(wallet); logger.info( `Role check for user ${userId} with wallet ${wallet}: ${isAdmin ? 'admin' : 'user'}` ); return isAdmin ? 'admin' : 'user'; } catch (error) { logger.error('Error checking user role:', error); return 'user'; } } // Проверка верификации Email async checkEmailVerification(code) { try { // Проверяем код через сервис верификации const result = await verificationService.verifyCode(code, 'email', null); if (!result.success) { return { verified: false }; } const userId = result.userId; const email = result.providerId; // Проверяем, существует ли пользователь с таким email const userResult = await db.query('SELECT * FROM users WHERE id = $1', [userId]); if (userResult.rows.length === 0) { return { verified: false }; } // Проверяем наличие кошелька и определяем роль const wallet = await this.getLinkedWallet(userId); let role = 'user'; // Базовая роль для доступа к чату if (wallet) { // Если есть кошелек, проверяем баланс токенов const isAdmin = await this.checkAdminRole(wallet); role = isAdmin ? 'admin' : 'user'; logger.info(`User ${userId} has wallet ${wallet}, role set to ${role}`); } else { logger.info(`User ${userId} has no wallet, using basic user role`); } return { verified: true, userId, email, role, wallet: wallet || null, }; } catch (error) { logger.error('Error checking email verification:', error); return { verified: false }; } } /** * Проверка Telegram аутентификации */ async verifyTelegramAuth(telegramId, verificationCode, session) { try { logger.info(`[verifyTelegramAuth] Starting for telegramId: ${telegramId}`); let userId; let isNewUser = false; // Проверяем наличие аутентифицированного пользователя в сессии if (session && session.authenticated && session.userId) { // Если есть авторизованный пользователь в сессии, связываем Telegram с ним userId = session.userId; logger.info( `[verifyTelegramAuth] Using existing authenticated user ${userId} from session` ); // Связываем Telegram с текущим пользователем await this.linkIdentity(userId, 'telegram', telegramId); return { success: true, userId, role: session.isAdmin ? 'admin' : 'user', telegramId, isNewUser: false, }; } // Если в сессии нет авторизованного пользователя, проверяем существующие идентификаторы // Проверяем, существует ли уже пользователь с таким Telegram ID const existingUserResult = await db.query( `SELECT u.*, ui.provider, ui.provider_id FROM users u JOIN user_identities ui ON u.id = ui.user_id WHERE ui.provider = 'telegram' AND ui.provider_id = $1`, [telegramId] ); // Если пользователь существует с таким telegramId, используем его if (existingUserResult.rows.length > 0) { const existingUser = existingUserResult.rows[0]; userId = existingUser.id; logger.info( `[verifyTelegramAuth] Found existing user ${userId} for Telegram ID ${telegramId}` ); } else { // Создаем нового пользователя для нового telegramId const newUserResult = await db.query('INSERT INTO users (role) VALUES ($1) RETURNING id', [ 'user', ]); userId = newUserResult.rows[0].id; isNewUser = true; // Добавляем Telegram идентификатор await db.query( 'INSERT INTO user_identities (user_id, provider, provider_id) VALUES ($1, $2, $3)', [userId, 'telegram', telegramId] ); logger.info( `[verifyTelegramAuth] Created new user ${userId} for Telegram ID ${telegramId}` ); } // Если есть гостевой ID в сессии, сохраняем его для нового пользователя if (session.guestId && isNewUser) { await db.query( 'INSERT INTO guest_user_mapping (user_id, guest_id) VALUES ($1, $2) ON CONFLICT (guest_id) DO UPDATE SET user_id = $1', [userId, session.guestId] ); logger.info(`[verifyTelegramAuth] Saved guest ID ${session.guestId} for user ${userId}`); } return { success: true, userId, role: 'user', telegramId, isNewUser, }; } catch (error) { logger.error('[verifyTelegramAuth] Error:', error); throw error; } } // Добавляем псевдоним функции checkAdminRole для обратной совместимости async checkAdminTokens(address) { if (!address) return false; logger.info(`Checking admin tokens for address: ${address}`); try { const isAdmin = await this.checkAdminRole(address); // Обновляем роль пользователя в базе данных, если есть админские токены if (isAdmin) { try { // Находим userId по адресу const userResult = await db.query( ` SELECT u.id FROM users u JOIN user_identities ui ON u.id = ui.user_id WHERE ui.provider = 'wallet' AND ui.provider_id = $1`, [address.toLowerCase()] ); if (userResult.rows.length > 0) { const userId = userResult.rows[0].id; // Обновляем роль пользователя await db.query('UPDATE users SET role = $1 WHERE id = $2', ['admin', userId]); logger.info(`Updated user ${userId} role to admin based on token holdings`); } } catch (error) { logger.error('Error updating user role:', error); // Продолжаем выполнение, даже если обновление роли не удалось } } else { // Если пользователь не является администратором, сбрасываем роль на "user", если она была "admin" try { const userResult = await db.query( ` SELECT u.id, u.role FROM users u JOIN user_identities ui ON u.id = ui.user_id WHERE ui.provider = 'wallet' AND ui.provider_id = $1`, [address.toLowerCase()] ); if (userResult.rows.length > 0 && userResult.rows[0].role === 'admin') { const userId = userResult.rows[0].id; await db.query('UPDATE users SET role = $1 WHERE id = $2', ['user', userId]); logger.info(`Reset user ${userId} role from admin to user (no tokens found)`); } } catch (error) { logger.error('Error updating user role:', error); } } return isAdmin; } catch (error) { logger.error(`Error in checkAdminTokens: ${error.message}`); return false; // При любой ошибке считаем, что пользователь не админ } } /** * Очистка старых гостевых идентификаторов * @param {number} userId - ID пользователя * @returns {Promise} */ async cleanupGuestIdentities(userId) { try { // Получаем все идентификаторы пользователя const identities = await this.getUserIdentities(userId); // Фильтруем только гостевые идентификаторы const guestIdentities = identities.filter((id) => id.identity_type === 'guest'); // Если гостевых идентификаторов больше 3, удаляем старые if (guestIdentities.length > 3) { // Сортируем по дате создания (новые первые) guestIdentities.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); // Оставляем только 3 последних идентификатора const identitiesToDelete = guestIdentities.slice(3); // Удаляем старые идентификаторы for (const identity of identitiesToDelete) { await db.query('DELETE FROM user_identities WHERE id = $1', [identity.id]); logger.info(`Deleted old guest identity: ${identity.identity_value}`); } } } catch (error) { logger.error('Error cleaning up guest identities:', error); } } /** * Получение всех идентификаторов пользователя * @param {number} userId - ID пользователя * @returns {Promise} - Массив идентификаторов */ async getUserIdentities(userId) { try { const result = await db.query( 'SELECT * FROM user_identities WHERE user_id = $1 ORDER BY created_at DESC', [userId] ); return result.rows; } catch (error) { logger.error('[getUserIdentities] Error:', error); throw error; } } /** * Проверка баланса токенов в сети Arbitrum с оптимизированным таймаутом * @param {string} address - Адрес кошелька * @returns {Promise} - Результат проверки баланса */ async checkArbitrumBalance(address) { const timeout = 2000; // Уменьшаем таймаут до 2 секунд try { const balance = await Promise.race([ this.getTokenBalance(address, ADMIN_CONTRACTS.ARBITRUM), new Promise((_, reject) => setTimeout(() => reject(new Error('TIMEOUT')), timeout)), ]); return { balance, hasTokens: balance > 0 }; } catch (error) { logger.warn(`[checkArbitrumBalance] Timeout or error for ${address}:`, error); return { balance: 0, hasTokens: false, error: error.message }; } } /** * Связывает новый идентификатор с существующим пользователем * @param {number} userId - ID пользователя * @param {string} provider - Тип идентификатора (wallet, email, telegram) * @param {string} providerId - Значение идентификатора * @returns {Promise} - Результат операции */ async linkIdentity(userId, provider, providerId) { try { if (!userId || !provider || !providerId) { logger.warn( `[AuthService] Missing parameters for linkIdentity: userId=${userId}, provider=${provider}, providerId=${providerId}` ); throw new Error('Missing parameters'); } // Нормализуем значение идентификатора let normalizedProviderId = providerId; if (provider === 'wallet') { // Для кошельков используем ethers для валидации и нормализации try { normalizedProviderId = ethers.getAddress(providerId).toLowerCase(); } catch (error) { logger.error(`[AuthService] Invalid wallet address: ${providerId}`, error); throw new Error('Invalid wallet address'); } } else if (provider === 'email') { normalizedProviderId = providerId.toLowerCase(); } logger.info( `[AuthService] Linking identity ${provider}:${normalizedProviderId} to user ${userId}` ); // Проверяем, существует ли уже такой идентификатор const existingResult = await db.query( `SELECT user_id FROM user_identities WHERE provider = $1 AND provider_id = $2`, [provider, normalizedProviderId] ); if (existingResult.rows.length > 0) { const existingUserId = existingResult.rows[0].user_id; // Если идентификатор уже принадлежит этому пользователю, ничего не делаем if (existingUserId === userId) { logger.info( `[AuthService] Identity ${provider}:${normalizedProviderId} already exists for user ${userId}` ); return { success: true, message: 'Identity already exists' }; } else { // Если идентификатор принадлежит другому пользователю, возвращаем ошибку logger.warn( `[AuthService] Identity ${provider}:${normalizedProviderId} already belongs to user ${existingUserId}, not user ${userId}` ); throw new Error(`Identity already belongs to another user (${existingUserId})`); } } // Добавляем новый идентификатор для пользователя await db.query( `INSERT INTO user_identities (user_id, provider, provider_id) VALUES ($1, $2, $3)`, [userId, provider, normalizedProviderId] ); // Проверяем и обновляем роль администратора, если это идентификатор кошелька let isAdmin = false; if (provider === 'wallet') { isAdmin = await this.checkAdminTokens(normalizedProviderId); // Обновляем роль пользователя в базе данных, если нужно if (isAdmin) { await db.query('UPDATE users SET role = $1 WHERE id = $2', ['admin', userId]); logger.info(`[AuthService] Updated user ${userId} role to admin based on token holdings`); } } logger.info( `[AuthService] Identity ${provider}:${normalizedProviderId} successfully linked to user ${userId}` ); return { success: true, isAdmin }; } catch (error) { logger.error( `[AuthService] Error linking identity ${provider}:${providerId} to user ${userId}:`, error ); throw error; } } /** * Обрабатывает успешную верификацию Email. * Находит или создает пользователя, связывает email, проверяет роль админа. * @param {string} email - Верифицированный email. * @param {object} session - Объект сессии запроса. * @returns {Promise<{userId: number, email: string, role: string, isNewUser: boolean}>} */ async handleEmailVerification(email, session) { const normalizedEmail = email.toLowerCase(); let userId; let isNewUser = false; let userRole = 'user'; // Роль по умолчанию try { // 1. Определить пользователя (существующий по email/сессии или новый) if (session.authenticated && session.userId) { // Используем уже аутентифицированного пользователя userId = session.userId; logger.info(`[handleEmailVerification] Using authenticated user ${userId}`); } else { // Ищем существующего пользователя по email const existingUser = await identityService.findUserByIdentity('email', normalizedEmail); if (existingUser) { userId = existingUser.id; logger.info(`[handleEmailVerification] Found existing user ${userId} by email ${normalizedEmail}`); } else if (session.tempUserId) { // Используем временного пользователя, если есть userId = session.tempUserId; logger.info(`[handleEmailVerification] Using temporary user ${userId}`); } else { // Создаем нового пользователя const newUserResult = await db.query('INSERT INTO users (role) VALUES ($1) RETURNING id', [ 'user', ]); userId = newUserResult.rows[0].id; isNewUser = true; logger.info(`[handleEmailVerification] Created new user ${userId}`); } } // 2. Связать email с пользователем (если еще не связан) await identityService.saveIdentity(userId, 'email', normalizedEmail, true); logger.info(`[handleEmailVerification] Ensured email identity ${normalizedEmail} for user ${userId}`); // 3. Связать гостевые ID (если есть) if (session.guestId) { await identityService.saveIdentity(userId, 'guest', session.guestId, true); } if (session.previousGuestId && session.previousGuestId !== session.guestId) { await identityService.saveIdentity(userId, 'guest', session.previousGuestId, true); } // 4. Проверить роль на основе привязанного кошелька try { const linkedWallet = await this.getLinkedWallet(userId); if (linkedWallet && linkedWallet.provider_id) { logger.info(`[handleEmailVerification] Found linked wallet ${linkedWallet.provider_id}. Checking role...`); const isAdmin = await this.checkAdminRole(linkedWallet.provider_id); userRole = isAdmin ? 'admin' : 'user'; logger.info(`[handleEmailVerification] Role determined as: ${userRole}`); // Опционально: Обновить роль в таблице users const currentUser = await db.query('SELECT role FROM users WHERE id = $1', [userId]); if (currentUser.rows.length > 0 && currentUser.rows[0].role !== userRole) { await db.query('UPDATE users SET role = $1 WHERE id = $2', [userRole, userId]); logger.info(`[handleEmailVerification] Updated user role in DB to ${userRole}`); } } else { logger.info(`[handleEmailVerification] No linked wallet found. Role remains 'user'.`); // Если кошелька нет, проверяем текущую роль из базы (на случай, если она была admin ранее) const currentUser = await db.query('SELECT role FROM users WHERE id = $1', [userId]); if (currentUser.rows.length > 0) { userRole = currentUser.rows[0].role; } } } catch (roleCheckError) { logger.error(`[handleEmailVerification] Error checking admin role:`, roleCheckError); // В случае ошибки берем текущую роль из базы или оставляем 'user' try { const currentUser = await db.query('SELECT role FROM users WHERE id = $1', [userId]); if (currentUser.rows.length > 0) { userRole = currentUser.rows[0].role; } } catch (dbError) { logger.error('Error fetching current user role after role check error:', dbError); } } // Очистка временных данных из сессии delete session.tempUserId; delete session.pendingEmail; return { userId, email: normalizedEmail, role: userRole, isNewUser, }; } catch (error) { logger.error('Error in handleEmailVerification:', error); throw new Error('Ошибка обработки верификации Email'); } } } // Создаем и экспортируем единственный экземпляр const authService = new AuthService(); module.exports = authService;