From 0c4eada51556c19dbbf9c828f7f056bb41a59f5c Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 28 May 2025 14:23:38 +0300 Subject: [PATCH] =?UTF-8?q?=D0=B2=D0=B0=D1=88=D0=B5=20=D1=81=D0=BE=D0=BE?= =?UTF-8?q?=D0=B1=D1=89=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BA=D0=BE=D0=BC=D0=BC?= =?UTF-8?q?=D0=B8=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/server.js | 4 +- backend/services/admin-role.js | 102 ++++++++++++++++++++++++++ backend/services/auth-service.js | 104 ++------------------------- backend/services/emailAuth.js | 3 +- backend/services/emailBot.js | 12 ++++ backend/services/identity-service.js | 3 +- backend/services/telegramBot.js | 3 +- 7 files changed, 129 insertions(+), 102 deletions(-) create mode 100644 backend/services/admin-role.js diff --git a/backend/server.js b/backend/server.js index 3d3e0fe..8c02ab6 100644 --- a/backend/server.js +++ b/backend/server.js @@ -40,8 +40,8 @@ async function initServices() { // Запуск email-бота console.log('Создаём экземпляр EmailBotService'); - // const emailBot = new EmailBotService(); - // await emailBot.start(); + const emailBot = new EmailBotService(); + await emailBot.start(); // Добавляем graceful shutdown process.once('SIGINT', async () => { diff --git a/backend/services/admin-role.js b/backend/services/admin-role.js new file mode 100644 index 0000000..0d20973 --- /dev/null +++ b/backend/services/admin-role.js @@ -0,0 +1,102 @@ +const { ethers } = require('ethers'); +const logger = require('../utils/logger'); +const authTokenService = require('./authTokenService'); +const rpcProviderService = require('./rpcProviderService'); + +// Минимальный ABI для ERC20 +const ERC20_ABI = [ + 'function balanceOf(address owner) view returns (uint256)' +]; + +/** + * Основной метод проверки роли админа + * @param {string} address - Адрес кошелька + * @returns {Promise} - Является ли пользователь админом + */ +async function checkAdminRole(address) { + if (!address) return false; + logger.info(`Checking admin role for address: ${address}`); + let foundTokens = false; + let errorCount = 0; + const balances = {}; + // Получаем токены и RPC из базы + const tokens = await authTokenService.getAllAuthTokens(); + const rpcProviders = await rpcProviderService.getAllRpcProviders(); + const rpcMap = {}; + for (const rpc of rpcProviders) { + rpcMap[rpc.network_id] = rpc.rpc_url; + } + const checkPromises = tokens.map(async (token) => { + try { + const rpcUrl = rpcMap[token.network]; + if (!rpcUrl) { + logger.error(`No RPC URL for network ${token.network}`); + balances[token.network] = 'Error: No RPC URL'; + errorCount++; + return null; + } + const provider = new ethers.JsonRpcProvider(rpcUrl); + // Проверяем доступность сети с таймаутом + 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 ${token.network} is not available: ${networkError.message}`); + balances[token.network] = 'Error: Network unavailable'; + errorCount++; + return null; + } + const tokenContract = new ethers.Contract(token.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[token.network] = formattedBalance; + logger.info(`Token balance on ${token.network}:`, { + address, + contract: token.address, + balance: formattedBalance, + minBalance: token.min_balance, + hasTokens: parseFloat(formattedBalance) >= parseFloat(token.min_balance), + }); + if (parseFloat(formattedBalance) >= parseFloat(token.min_balance)) { + logger.info(`Found admin tokens on ${token.network}`); + foundTokens = true; + } + return { network: token.network, balance: formattedBalance }; + } catch (error) { + logger.error(`Error checking balance in ${token.network}:`, { + address, + contract: token.address, + error: error.message || 'Unknown error', + }); + balances[token.network] = 'Error'; + errorCount++; + return null; + } + }); + await Promise.all(checkPromises); + if (errorCount === tokens.length) { + 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) => parseFloat(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; +} + +module.exports = { checkAdminRole }; \ No newline at end of file diff --git a/backend/services/auth-service.js b/backend/services/auth-service.js index 1c5dbb5..bd0ada1 100644 --- a/backend/services/auth-service.js +++ b/backend/services/auth-service.js @@ -8,6 +8,7 @@ const identityService = require('./identity-service'); // <-- ДОБАВЛЕН const authTokenService = require('./authTokenService'); const rpcProviderService = require('./rpcProviderService'); const { getLinkedWallet } = require('./wallet-service'); +const { checkAdminRole } = require('./admin-role'); const ERC20_ABI = ['function balanceOf(address owner) view returns (uint256)']; @@ -57,7 +58,7 @@ class AuthService { const user = userResult.rows[0]; // Проверяем роль администратора при каждой аутентификации - const isAdmin = await this.checkAdminRole(normalizedAddress); + const isAdmin = await checkAdminRole(normalizedAddress); // Если статус админа изменился, обновляем роль в базе данных if (user.role === 'admin' && !isAdmin) { @@ -90,7 +91,7 @@ class AuthService { ); // Проверяем, есть ли у пользователя роль админа - const isAdmin = await this.checkAdminRole(normalizedAddress); + const isAdmin = await checkAdminRole(normalizedAddress); logger.info(`New user ${userId} role check result: ${isAdmin ? 'admin' : 'user'}`); // Если у пользователя есть админские токены, обновляем его роль @@ -108,97 +109,6 @@ class AuthService { } } - /** - * Основной метод проверки роли админа - * @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 = {}; - // Получаем токены и RPC из базы - const tokens = await authTokenService.getAllAuthTokens(); - const rpcProviders = await rpcProviderService.getAllRpcProviders(); - const rpcMap = {}; - for (const rpc of rpcProviders) { - rpcMap[rpc.network_id] = rpc.rpc_url; - } - const checkPromises = tokens.map(async (token) => { - try { - const rpcUrl = rpcMap[token.network]; - if (!rpcUrl) { - logger.error(`No RPC URL for network ${token.network}`); - balances[token.network] = 'Error: No RPC URL'; - errorCount++; - return null; - } - const provider = new ethers.JsonRpcProvider(rpcUrl); - // Проверяем доступность сети с таймаутом - 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 ${token.network} is not available: ${networkError.message}`); - balances[token.network] = 'Error: Network unavailable'; - errorCount++; - return null; - } - const tokenContract = new ethers.Contract(token.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[token.network] = formattedBalance; - logger.info(`Token balance on ${token.network}:`, { - address, - contract: token.address, - balance: formattedBalance, - minBalance: token.min_balance, - hasTokens: parseFloat(formattedBalance) >= parseFloat(token.min_balance), - }); - if (parseFloat(formattedBalance) >= parseFloat(token.min_balance)) { - logger.info(`Found admin tokens on ${token.network}`); - foundTokens = true; - } - return { network: token.network, balance: formattedBalance }; - } catch (error) { - logger.error(`Error checking balance in ${token.network}:`, { - address, - contract: token.address, - error: error.message || 'Unknown error', - }); - balances[token.network] = 'Error'; - errorCount++; - return null; - } - }); - await Promise.all(checkPromises); - if (errorCount === tokens.length) { - 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) => parseFloat(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 - Адрес кошелька @@ -378,7 +288,7 @@ class AuthService { } // Если есть кошелек, проверяем админские токены - const isAdmin = await this.checkAdminRole(wallet); + const isAdmin = await checkAdminRole(wallet); logger.info( `Role check for user ${userId} with wallet ${wallet}: ${isAdmin ? 'admin' : 'user'}` ); @@ -415,7 +325,7 @@ class AuthService { if (wallet) { // Если есть кошелек, проверяем баланс токенов - const isAdmin = await this.checkAdminRole(wallet); + const isAdmin = await checkAdminRole(wallet); role = isAdmin ? 'admin' : 'user'; logger.info(`User ${userId} has wallet ${wallet}, role set to ${role}`); } else { @@ -530,7 +440,7 @@ class AuthService { logger.info(`Checking admin tokens for address: ${address}`); try { - const isAdmin = await this.checkAdminRole(address); + const isAdmin = await checkAdminRole(address); // Обновляем роль пользователя в базе данных, если есть админские токены if (isAdmin) { @@ -798,7 +708,7 @@ class AuthService { const linkedWallet = await 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); + const isAdmin = await checkAdminRole(linkedWallet.provider_id); userRole = isAdmin ? 'admin' : 'user'; logger.info(`[handleEmailVerification] Role determined as: ${userRole}`); diff --git a/backend/services/emailAuth.js b/backend/services/emailAuth.js index 57b689a..ed1cee1 100644 --- a/backend/services/emailAuth.js +++ b/backend/services/emailAuth.js @@ -4,6 +4,7 @@ const logger = require('../utils/logger'); const EmailBotService = require('./emailBot.js'); const db = require('../db'); const authService = require('./auth-service'); +const { checkAdminRole } = require('./admin-role'); class EmailAuth { constructor() { @@ -167,7 +168,7 @@ class EmailAuth { const linkedWallet = await authService.getLinkedWallet(finalUserId); if (linkedWallet) { logger.info(`[checkEmailVerification] Found linked wallet ${linkedWallet} for user ${finalUserId}. Checking admin role...`); - const isAdmin = await authService.checkAdminRole(linkedWallet); + const isAdmin = await checkAdminRole(linkedWallet); userRole = isAdmin ? 'admin' : 'user'; logger.info(`[checkEmailVerification] Role for user ${finalUserId} determined as: ${userRole}`); diff --git a/backend/services/emailBot.js b/backend/services/emailBot.js index 7ab25c6..fed7dff 100644 --- a/backend/services/emailBot.js +++ b/backend/services/emailBot.js @@ -236,6 +236,7 @@ class EmailBotService { logger.info('[EmailBot] IMAP config:', safeConfig); let attempt = 0; const maxAttempts = 3; + this.isChecking = false; const tryConnect = () => { attempt++; logger.info(`[EmailBot] IMAP connect attempt ${attempt}`); @@ -253,6 +254,17 @@ class EmailBotService { // После успешного подключения — обычная логика this.checkEmails(); logger.info('[EmailBot] Email bot started and IMAP connection initiated'); + // Периодическая проверка почты + setInterval(async () => { + if (this.isChecking) return; + this.isChecking = true; + try { + await this.checkEmails(); + } catch (e) { + logger.error('[EmailBot] Error in periodic checkEmails:', e); + } + this.isChecking = false; + }, 60000); // 60 секунд }); this.imap.once('error', (err) => { logger.error(`[EmailBot] IMAP connection error: ${err.message}`); diff --git a/backend/services/identity-service.js b/backend/services/identity-service.js index 00d1603..94dfb1f 100644 --- a/backend/services/identity-service.js +++ b/backend/services/identity-service.js @@ -1,6 +1,7 @@ const db = require('../db'); const logger = require('../utils/logger'); const { getLinkedWallet } = require('./wallet-service'); +const { checkAdminRole } = require('./admin-role'); /** * Сервис для работы с идентификаторами пользователей @@ -545,7 +546,7 @@ class IdentityService { const wallet = await getLinkedWallet(user.id); let role = 'user'; if (wallet) { - const isAdmin = await authService.checkAdminRole(wallet); + const isAdmin = await checkAdminRole(wallet); role = isAdmin ? 'admin' : 'user'; // Обновляем роль в users, если изменилась if (user.role !== role) { diff --git a/backend/services/telegramBot.js b/backend/services/telegramBot.js index 2138aaa..6bad442 100644 --- a/backend/services/telegramBot.js +++ b/backend/services/telegramBot.js @@ -6,6 +6,7 @@ const verificationService = require('./verification-service'); const crypto = require('crypto'); const identityService = require('./identity-service'); const aiAssistant = require('./ai-assistant'); +const { checkAdminRole } = require('./admin-role'); let botInstance = null; let telegramSettingsCache = null; @@ -158,7 +159,7 @@ async function getBot() { const linkedWallet = await authService.getLinkedWallet(userId); if (linkedWallet) { logger.info(`[TelegramBot] Found linked wallet ${linkedWallet} for user ${userId}. Checking role...`); - const isAdmin = await authService.checkAdminRole(linkedWallet); + const isAdmin = await checkAdminRole(linkedWallet); userRole = isAdmin ? 'admin' : 'user'; logger.info(`[TelegramBot] Role for user ${userId} determined as: ${userRole}`);