From 5e062c8d9b1d6012ca4bc04135834021afaaeb11 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 22 Apr 2025 17:41:05 +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/routes/auth.js | 240 +++++++++++++++------------ backend/services/auth-service.js | 128 +++++++++++++- backend/services/emailAuth.js | 26 +++ backend/services/identity-service.js | 43 +++++ backend/services/telegramBot.js | 113 ++++++++++--- frontend/src/assets/styles/home.css | 12 +- frontend/src/composables/useAuth.js | 14 ++ frontend/src/services/tokens.js | 11 +- frontend/src/views/HomeView.vue | 212 ++++++++++++----------- 9 files changed, 548 insertions(+), 251 deletions(-) diff --git a/backend/routes/auth.js b/backend/routes/auth.js index a4f7ceb..ea1a21a 100644 --- a/backend/routes/auth.js +++ b/backend/routes/auth.js @@ -194,6 +194,38 @@ router.post('/telegram/verify', async (req, res) => { }); } + // ---> ШАГ 4 И 5: ПОИСК ПРИВЯЗАННОГО КОШЕЛЬКА И ПРОВЕРКА БАЛАНСА <--- + let linkedWalletAddress = null; + let finalIsAdmin = false; // Роль по умолчанию + + try { + const walletIdentity = await identityService.findIdentity(verificationResult.userId, 'wallet'); + if (walletIdentity) { + linkedWalletAddress = walletIdentity.provider_id; + logger.info(`[telegram/verify] Found linked wallet ${linkedWalletAddress} for user ${verificationResult.userId}`); + + // Проверяем баланс токенов для определения роли + finalIsAdmin = await authService.checkAdminTokens(linkedWalletAddress); + logger.info(`[telegram/verify] Admin status based on token balance for ${linkedWalletAddress}: ${finalIsAdmin}`); + + // Обновляем роль в БД, если она отличается от той, что была получена из verifyTelegramAuth + const currentRoleInDb = verificationResult.role === 'admin'; + if (finalIsAdmin !== currentRoleInDb) { + await db.query('UPDATE users SET role = $1 WHERE id = $2', [finalIsAdmin ? 'admin' : 'user', verificationResult.userId]); + logger.info(`[telegram/verify] User role updated in DB for user ${verificationResult.userId} to ${finalIsAdmin ? 'admin' : 'user'}`); + } + } else { + logger.info(`[telegram/verify] No linked wallet found for user ${verificationResult.userId}. Role remains '${verificationResult.role}'`); + // Если кошелек не найден, используем роль из verificationResult (скорее всего 'user') + finalIsAdmin = verificationResult.role === 'admin'; + } + } catch (error) { + logger.error(`[telegram/verify] Error finding linked wallet or checking tokens for user ${verificationResult.userId}:`, error); + // В случае ошибки, используем роль из verificationResult + finalIsAdmin = verificationResult.role === 'admin'; + } + // ---> КОНЕЦ ШАГОВ 4 И 5 <--- + // Создаем новую сессию для этого telegramId req.session.regenerate(async (err) => { if (err) { @@ -209,7 +241,13 @@ router.post('/telegram/verify', async (req, res) => { req.session.telegramId = telegramId; req.session.authType = 'telegram'; req.session.authenticated = true; - req.session.role = verificationResult.role; + req.session.isAdmin = finalIsAdmin; // <-- УСТАНАВЛИВАЕМ РОЛЬ ПОСЛЕ ПРОВЕРКИ БАЛАНСА + + // ---> ДОБАВЛЯЕМ АДРЕС КОШЕЛЬКА В СЕССИЮ (ЕСЛИ НАЙДЕН) <--- + if (linkedWalletAddress) { + req.session.address = linkedWalletAddress; + } + // ---> КОНЕЦ ДОБАВЛЕНИЯ АДРЕСА <--- // Восстанавливаем гостевой ID, если он был if (guestId) { @@ -227,9 +265,10 @@ router.post('/telegram/verify', async (req, res) => { return res.json({ success: true, userId: verificationResult.userId, - role: verificationResult.role, + isAdmin: finalIsAdmin, // <-- ВОЗВРАЩАЕМ АКТУАЛЬНУЮ РОЛЬ telegramId, isNewUser: verificationResult.isNewUser, + address: linkedWalletAddress || null // <-- ВОЗВРАЩАЕМ АДРЕС КОШЕЛЬКА }); }); } catch (error) { @@ -285,11 +324,12 @@ router.post('/email/verify-code', async (req, res) => { }); } - // Если email передан в запросе, сохраняем его в сессии + // Если email передан в запросе, сохраняем его в сессии для проверки кода if (email && !req.session.pendingEmail) { req.session.pendingEmail = email.toLowerCase(); } + // Нужен email в сессии для проверки кода if (!req.session.pendingEmail) { return res.status(400).json({ success: false, @@ -297,135 +337,115 @@ router.post('/email/verify-code', async (req, res) => { }); } - // Сохраняем гостевой ID до проверки + // Сохраняем гостевые ID до обработки const guestId = req.session.guestId; const previousGuestId = req.session.previousGuestId; - // Проверяем код через сервис верификации - const verificationResult = await verificationService.verifyCode( + // 1. Проверяем сам код верификации + const codeVerificationResult = await verificationService.verifyCode( code, 'email', req.session.pendingEmail ); - if (!verificationResult.success) { + if (!codeVerificationResult.success) { return res.status(400).json({ success: false, - error: verificationResult.error || 'Неверный код подтверждения', + error: codeVerificationResult.error || 'Неверный код подтверждения', }); } - // Получаем или создаем пользователя - let userId; - let isNewAuth = false; + // 2. Обрабатываем аутентификацию/привязку и проверяем роль через AuthService + const authResult = await authService.handleEmailVerification( + req.session.pendingEmail, // Передаем email из сессии + req.session // Передаем всю сессию + ); - // Проверяем, авторизован ли пользователь - if (req.session.authenticated && req.session.userId) { - // Связываем email с существующим пользователем - userId = req.session.userId; - logger.info( - `[email/verify-code] Linking email ${req.session.pendingEmail} to existing authenticated user ${userId}` - ); + // ---> ДОБАВЛЯЕМ ПОЛУЧЕНИЕ И СОХРАНЕНИЕ АДРЕСА КОШЕЛЬКА В СЕССИЮ <--- + let linkedWalletAddress = null; + if (authResult.userId) { + try { + const walletIdentity = await identityService.findIdentity(authResult.userId, 'wallet'); + if (walletIdentity) { + linkedWalletAddress = walletIdentity.provider_id; + } + } catch (walletError) { + logger.warn(`[email/verify-code] Could not fetch linked wallet for user ${authResult.userId}:`, walletError); + } + } + // ---> КОНЕЦ ДОБАВЛЕНИЯ <--- - // Связываем email с текущим аккаунтом - const linkResult = await authService.linkIdentity(userId, 'email', req.session.pendingEmail); + // ---> ОПРЕДЕЛЯЕМ РОЛЬ НА ОСНОВЕ БАЛАНСА ПРИВЯЗАННОГО КОШЕЛЬКА <--- + let finalIsAdmin = false; // Роль по умолчанию + if (linkedWalletAddress) { + try { + finalIsAdmin = await authService.checkAdminTokens(linkedWalletAddress); + logger.info(`[email/verify-code] Admin status based on token balance for ${linkedWalletAddress}: ${finalIsAdmin}`); - // Сохраняем email в сессии - req.session.email = req.session.pendingEmail; - - // Удаляем временные данные - delete req.session.pendingEmail; - - // Сохраняем сессию - await sessionService.saveSession(req.session); - - return res.json({ - success: true, - userId, - email: req.session.email, - authenticated: true, - linked: true, - }); + // Обновляем роль в БД, если она отличается от текущей + const currentRole = authResult.role === 'admin'; + if (finalIsAdmin !== currentRole) { + await db.query('UPDATE users SET role = $1 WHERE id = $2', [finalIsAdmin ? 'admin' : 'user', authResult.userId]); + logger.info(`[email/verify-code] User role updated in DB for user ${authResult.userId} to ${finalIsAdmin ? 'admin' : 'user'}`); + } + } catch (tokenCheckError) { + logger.error(`[email/verify-code] Error checking admin tokens for ${linkedWalletAddress}:`, tokenCheckError); + // В случае ошибки проверки токенов, используем роль из authResult + finalIsAdmin = authResult.role === 'admin'; + } } else { - // Если пользователь не авторизован, ищем существующего пользователя или создаем нового - - // Ищем существующего пользователя по email - const existingUser = await identityService.findUserByIdentity( - 'email', - req.session.pendingEmail - ); - - if (existingUser) { - // Используем существующего пользователя - userId = existingUser.id; - logger.info( - `[email/verify-code] Using existing user ${userId} with email ${req.session.pendingEmail}` - ); - } else if (req.session.userId) { - // Используем текущего пользователя - userId = req.session.userId; - logger.info( - `[email/verify-code] Using current user ${userId} for email ${req.session.pendingEmail}` - ); - } else if (req.session.tempUserId) { - // Используем временного пользователя - userId = req.session.tempUserId; - logger.info( - `[email/verify-code] Using temporary user ${userId} for email ${req.session.pendingEmail}` - ); - } else { - // Создаем нового пользователя - const newUser = await db.query('INSERT INTO users (role) VALUES ($1) RETURNING id', [ - 'user', - ]); - userId = newUser.rows[0].id; - isNewAuth = true; - logger.info( - `[email/verify-code] Created new user ${userId} for email ${req.session.pendingEmail}` - ); - } - - // Сохраняем email как идентификатор - await identityService.saveIdentity(userId, 'email', req.session.pendingEmail, true); - - // Сохраняем гостевые идентификаторы - if (guestId) { - await identityService.saveIdentity(userId, 'guest', guestId, true); - } - - if (previousGuestId && previousGuestId !== guestId) { - await identityService.saveIdentity(userId, 'guest', previousGuestId, true); - } - - // Устанавливаем сессию - req.session.userId = userId; - req.session.authenticated = true; - req.session.authType = 'email'; - req.session.email = req.session.pendingEmail; - - // Удаляем временные данные - delete req.session.tempUserId; - delete req.session.pendingEmail; - - // Сохраняем сессию - await sessionService.saveSession(req.session); - - // Связываем гостевые сообщения - await sessionService.linkGuestMessages(req.session, userId); - - return res.json({ - success: true, - userId, - email: req.session.email, - authenticated: true, - isNewAuth, - }); + // Если кошелек не привязан, используем роль из authResult (вероятно, 'user') + finalIsAdmin = authResult.role === 'admin'; + logger.info(`[email/verify-code] No linked wallet found for user ${authResult.userId}. Using role from authResult: ${authResult.role}`); } + // ---> КОНЕЦ ОПРЕДЕЛЕНИЯ РОЛИ <--- + + // 3. Устанавливаем сессию на основе результата + req.session.userId = authResult.userId; + req.session.authenticated = true; + req.session.authType = 'email'; + req.session.email = authResult.email; + req.session.isAdmin = finalIsAdmin; // <-- УСТАНАВЛИВАЕМ РОЛЬ ПОСЛЕ ПРОВЕРКИ БАЛАНСА + // ---> ДОБАВЛЯЕМ АДРЕС КОШЕЛЬКА В СЕССИЮ <--- + if (linkedWalletAddress) { + req.session.address = linkedWalletAddress; + } + // ---> КОНЕЦ ДОБАВЛЕНИЯ <--- + + // Восстанавливаем/обновляем гостевые ID в сессии, если они были + if (guestId) req.session.guestId = guestId; + if (previousGuestId) req.session.previousGuestId = previousGuestId; + + // Очищаем временные данные (authService уже должен был это сделать, но на всякий случай) + delete req.session.tempUserId; + delete req.session.pendingEmail; + + // Сохраняем обновленную сессию + await sessionService.saveSession(req.session); + + // Связываем гостевые сообщения + await sessionService.linkGuestMessages(req.session, authResult.userId); + + // 4. Отправляем ответ + return res.json({ + success: true, + userId: authResult.userId, + email: authResult.email, + isAdmin: finalIsAdmin, // <-- ВОЗВРАЩАЕМ АКТУАЛЬНУЮ РОЛЬ + authenticated: true, + isNewAuth: authResult.isNewUser, + address: linkedWalletAddress || null // <-- ВОЗВРАЩАЕМ АДРЕС КОШЕЛЬКА + }); + } catch (error) { logger.error('[email/verify-code] Error:', error); + // Проверяем, является ли ошибка созданной нами в authService + const errorMessage = error.message === 'Ошибка обработки верификации Email' + ? error.message + : 'Ошибка сервера'; return res.status(500).json({ success: false, - error: 'Ошибка сервера', + error: errorMessage, }); } }); diff --git a/backend/services/auth-service.js b/backend/services/auth-service.js index 00c4d4e..9eb2ea5 100644 --- a/backend/services/auth-service.js +++ b/backend/services/auth-service.js @@ -4,6 +4,7 @@ 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' }, @@ -385,13 +386,22 @@ class AuthService { // Получение связанного кошелька async getLinkedWallet(userId) { - const result = await db.query( - `SELECT provider_id as address - FROM user_identities - WHERE user_id = $1 AND provider = 'wallet'`, - [userId] - ); - return result.rows[0]?.address; + 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; + } } /** @@ -774,6 +784,110 @@ class AuthService { 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'); + } + } } // Создаем и экспортируем единственный экземпляр diff --git a/backend/services/emailAuth.js b/backend/services/emailAuth.js index 96e2eb2..63f57f4 100644 --- a/backend/services/emailAuth.js +++ b/backend/services/emailAuth.js @@ -161,6 +161,31 @@ class EmailAuth { await authService.identityService.saveIdentity(finalUserId, 'email', email, true); logger.info(`[checkEmailVerification] Added email identity ${email} for user ${finalUserId}`); + // ----> НАЧАЛО: Проверка роли на основе привязанного кошелька + let userRole = 'user'; // Роль по умолчанию + try { + 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); + userRole = isAdmin ? 'admin' : 'user'; + logger.info(`[checkEmailVerification] Role for user ${finalUserId} determined as: ${userRole}`); + + // Опционально: Обновить роль в таблице users, если она отличается + const currentUser = await db.query('SELECT role FROM users WHERE id = $1', [finalUserId]); + if (currentUser.rows.length > 0 && currentUser.rows[0].role !== userRole) { + await db.query('UPDATE users SET role = $1 WHERE id = $2', [userRole, finalUserId]); + logger.info(`[checkEmailVerification] Updated user role in DB to ${userRole}`); + } + } else { + logger.info(`[checkEmailVerification] No linked wallet found for user ${finalUserId}. Role remains 'user'.`); + } + } catch (roleCheckError) { + logger.error(`[checkEmailVerification] Error checking admin role for user ${finalUserId}:`, roleCheckError); + // В случае ошибки оставляем роль 'user' + } + // ----> КОНЕЦ: Проверка роли + // Если есть гостевой ID, добавляем его тоже if (session.guestId) { await authService.identityService.saveIdentity(finalUserId, 'guest', session.guestId, true); @@ -179,6 +204,7 @@ class EmailAuth { verified: true, userId: finalUserId, email: email, + role: userRole, }; } catch (error) { logger.error('Error checking email verification:', error); diff --git a/backend/services/identity-service.js b/backend/services/identity-service.js index 6691aa5..75fe11f 100644 --- a/backend/services/identity-service.js +++ b/backend/services/identity-service.js @@ -238,6 +238,49 @@ class IdentityService { } } + /** + * Находит конкретный идентификатор пользователя по его типу. + * Возвращает первую найденную запись. + * @param {number} userId - ID пользователя + * @param {string} provider - Тип идентификатора (например, 'wallet', 'email') + * @returns {Promise} - Объект идентификатора (содержит provider_id) или null + */ + async findIdentity(userId, provider) { + try { + if (!userId || !provider) { + logger.warn(`[IdentityService] Missing parameters for findIdentity: userId=${userId}, provider=${provider}`); + return null; + } + + // Нормализуем провайдера + const normalizedProvider = provider.toLowerCase(); + + const result = await db.query( + `SELECT provider, provider_id, created_at, updated_at + FROM user_identities + WHERE user_id = $1 AND provider = $2 + LIMIT 1`, + [userId, normalizedProvider] + ); + + if (result.rows.length === 0) { + logger.info(`[IdentityService] No ${normalizedProvider} identity found for user ${userId}`); + return null; + } + + logger.info( + `[IdentityService] Found ${normalizedProvider} identity for user ${userId}: ${result.rows[0].provider_id}` + ); + return result.rows[0]; // Возвращаем всю строку (включая provider_id) + } catch (error) { + logger.error( + `[IdentityService] Error finding ${provider} identity for user ${userId}:`, + error + ); + return null; + } + } + /** * Сохраняет идентификаторы из сессии для пользователя * @param {object} session - Объект сессии diff --git a/backend/services/telegramBot.js b/backend/services/telegramBot.js index a06250a..dce7506 100644 --- a/backend/services/telegramBot.js +++ b/backend/services/telegramBot.js @@ -41,6 +41,7 @@ async function getBot() { const providerId = verification.provider_id; const linkedUserId = verification.user_id; // Получаем связанный userId если он есть let userId; + let userRole = 'user'; // Роль по умолчанию // Отмечаем код как использованный await db.query('UPDATE verification_codes SET used = true WHERE id = $1', [ @@ -134,35 +135,95 @@ async function getBot() { } } - // Логируем guestId перед обновлением сессии - logger.info(`[telegramBot] Attempting to update session for guestId: ${providerId}`); + // ----> НАЧАЛО: Проверка роли на основе привязанного кошелька <---- + if (userId) { // Убедимся, что userId определен + logger.info(`[TelegramBot] Checking linked wallet for determined userId: ${userId} (Type: ${typeof userId})`); + try { + 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); + userRole = isAdmin ? 'admin' : 'user'; + logger.info(`[TelegramBot] Role for user ${userId} determined as: ${userRole}`); - // Обновляем сессию в базе данных - const updateResult = await db.query( - `UPDATE session - SET sess = (sess::jsonb || $1::jsonb)::json - WHERE sess::jsonb @> $2::jsonb`, - [ - JSON.stringify({ - userId: userId.toString(), - authenticated: true, - authType: 'telegram', - telegramId: ctx.from.id.toString(), - // Добавляем имя и юзернейм из Telegram - telegramUsername: ctx.from.username, - telegramFirstName: ctx.from.first_name, - }), - JSON.stringify({ guestId: providerId }), - ] - ); - - // Логируем результат обновления сессии - if (updateResult.rowCount > 0) { - logger.info(`Session updated successfully for guestId: ${providerId}, userId: ${userId}`); + // Опционально: Обновить роль в таблице 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(`[TelegramBot] Updated user role in DB to ${userRole}`); + } + } else { + logger.info(`[TelegramBot] No linked wallet found for user ${userId}. Checking current DB role.`); + // Если кошелька нет, берем текущую роль из базы + 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(`[TelegramBot] Error checking admin role for user ${userId}:`, 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) { /* ignore */ } + } } else { - logger.warn( - `Session update failed: No session found or updated for guestId: ${providerId}. User ${userId} authenticated via Telegram, but web session might not reflect it.` + logger.error('[TelegramBot] Cannot check role because userId is undefined!'); + } + // ----> КОНЕЦ: Проверка роли <---- + + // Логируем userId перед обновлением сессии + logger.info(`[telegramBot] Attempting to update session for userId: ${userId}`); + + // Находим последнюю активную сессию для данного userId + let activeSessionId = null; + try { + // Ищем сессию, где есть userId и она не истекла (проверка expires_at) + // Сортируем по expires_at DESC чтобы взять самую "свежую", если их несколько + const sessionResult = await db.query( + `SELECT sid FROM session + WHERE sess ->> 'userId' = $1 + AND expire > NOW() + ORDER BY expire DESC + LIMIT 1`, + [userId?.toString()] // Используем optional chaining и преобразуем в строку ); + + if (sessionResult.rows.length > 0) { + activeSessionId = sessionResult.rows[0].sid; + logger.info(`[telegramBot] Found active session ID ${activeSessionId} for user ${userId}`); + + // Обновляем найденную сессию в базе данных, добавляя/перезаписывая данные Telegram + const updateResult = await db.query( + `UPDATE session + SET sess = (sess::jsonb || $1::jsonb)::json + WHERE sid = $2`, + [ + JSON.stringify({ + // authenticated: true, // Не перезаписываем, т.к. сессия уже должна быть аутентифицирована + authType: 'telegram', // Обновляем тип аутентификации + telegramId: ctx.from.id.toString(), + telegramUsername: ctx.from.username, + telegramFirstName: ctx.from.first_name, + role: userRole, // Записываем определенную роль + // userId: userId?.toString() // userId уже должен быть в сессии + }), + activeSessionId // Обновляем по найденному session ID + ] + ); + + if (updateResult.rowCount > 0) { + logger.info(`[telegramBot] Session ${activeSessionId} updated successfully with Telegram data for user ${userId}`); + } else { + logger.warn(`[telegramBot] Session update query executed but did not update rows for sid: ${activeSessionId}. This might indicate a concurrency issue or incorrect sid.`); + } + + } else { + logger.warn(`[telegramBot] No active web session found for userId: ${userId}. Telegram is linked, but the user might need to refresh their browser session.`); + } + } catch(sessionError) { + logger.error(`[telegramBot] Error finding or updating session for userId ${userId}:`, sessionError); } // Отправляем сообщение об успешной аутентификации diff --git a/frontend/src/assets/styles/home.css b/frontend/src/assets/styles/home.css index ef95002..51f4c2a 100644 --- a/frontend/src/assets/styles/home.css +++ b/frontend/src/assets/styles/home.css @@ -300,7 +300,6 @@ input, textarea { display: flex; flex-direction: column; margin: var(--spacing-lg) 0 35px 0; - height: calc(100vh - 195px); min-height: 500px; position: relative; } @@ -308,10 +307,8 @@ input, textarea { .chat-messages { display: flex; flex-direction: column; - flex: 1; overflow-y: auto; padding: var(--spacing-lg); - margin-bottom: var(--spacing-lg); background: var(--color-white); border-radius: var(--radius-lg); border: 1px solid var(--color-grey-light); @@ -319,19 +316,21 @@ input, textarea { top: 0; left: 0; right: 0; - bottom: 135px; + bottom: 205px; transition: bottom var(--transition-normal); } /* Адаптация позиции при активации фокуса */ .chat-container:has(.chat-input.focused) .chat-messages { - bottom: 235px; + bottom: 305px; } /* Реализуем программное изменение позиции через JS для браузеров без поддержки :has */ @media (max-width: 100vw) { + /* Примечание: Этот блок @media может потребовать обновления в JS коде, */ + /* если он используется для имитации :has. Сейчас изменяем только CSS. */ .chat-input.focused ~ .messages-container { - bottom: 235px; + bottom: 305px; } } @@ -435,7 +434,6 @@ input, textarea { min-height: var(--chat-input-min-height); max-height: var(--chat-input-max-height); transition: min-height var(--transition-normal), padding var(--transition-normal); - margin-bottom: var(--spacing-md); z-index: 10; box-shadow: none; } diff --git a/frontend/src/composables/useAuth.js b/frontend/src/composables/useAuth.js index 899fafd..57028e2 100644 --- a/frontend/src/composables/useAuth.js +++ b/frontend/src/composables/useAuth.js @@ -32,8 +32,22 @@ export function useAuth() { return acc; }, []); + // Сравниваем новый отфильтрованный список с текущим значением + const currentProviders = identities.value.map(id => id.provider).sort(); + const newProviders = filteredIdentities.map(id => id.provider).sort(); + + const identitiesChanged = JSON.stringify(currentProviders) !== JSON.stringify(newProviders); + + // Обновляем реактивное значение identities.value = filteredIdentities; console.log('User identities updated:', identities.value); + + // Если список идентификаторов изменился, принудительно проверяем аутентификацию, + // чтобы обновить authType и другие связанные данные (например, telegramId) + if (identitiesChanged) { + console.log('Identities changed, forcing auth check.'); + await checkAuth(); // Вызываем checkAuth для обновления полного состояния + } } } catch (error) { console.error('Error fetching user identities:', error); diff --git a/frontend/src/services/tokens.js b/frontend/src/services/tokens.js index 2b7344d..72d32e6 100644 --- a/frontend/src/services/tokens.js +++ b/frontend/src/services/tokens.js @@ -25,9 +25,16 @@ export const TOKEN_CONTRACTS = { }; // Получение балансов токенов -export const fetchTokenBalances = async () => { +export const fetchTokenBalances = async (address = null) => { try { - const response = await api.get('/api/tokens/balances'); + let url = '/api/tokens/balances'; + if (address) { + url += `?address=${encodeURIComponent(address)}`; + console.log(`Fetching token balances for specific address: ${address}`); + } else { + console.log('Fetching token balances for session user'); + } + const response = await api.get(url); return response.data; } catch (error) { console.error('Error fetching token balances:', error); diff --git a/frontend/src/views/HomeView.vue b/frontend/src/views/HomeView.vue index 074dbe8..353122c 100644 --- a/frontend/src/views/HomeView.vue +++ b/frontend/src/views/HomeView.vue @@ -207,18 +207,24 @@ -
-
-
- - +
+ +
+
+
+ + +
+ - - - -
- + - + - +
Код верификации: - {{ - telegramAuth.verificationCode - }} + {{ telegramAuth.verificationCode }} Скопировано!
- Открыть бота Telegram + Открыть бота Telegram
+ +
-
+

Баланс токенов:

ETH: @@ -1110,49 +1105,57 @@ // Создаем интервал для проверки состояния авторизации telegramAuth.value.checkInterval = setInterval(async () => { try { + // ВАЖНО: Используем auth.checkAuth() из useAuth для получения актуального состояния + // Не нужно делать отдельный axios запрос здесь const checkResponse = await auth.checkAuth(); - // Получаем Telegram ID из проверки аутентификации - const telegramId = checkResponse.telegramId; + // Получаем Telegram ID из состояния auth (которое обновилось через checkAuth) + const telegramId = auth.telegramId.value; // Используем реактивное значение if (auth.isAuthenticated.value && telegramId) { + console.log('[handleTelegramAuth] Telegram successfully linked/verified. Clearing interval and hiding form.'); + clearTelegramInterval(); // <--- ДОБАВЛЕНО: Останавливаем интервал + telegramAuth.value.showVerification = false; // <--- ДОБАВЛЕНО: Скрываем форму верификации + telegramAuth.value.verificationCode = ''; // Очищаем код на всякий случай + telegramAuth.value.error = ''; // Очищаем ошибки + + // ... остальная логика обработки успешной привязки ... + let roleUpdated = false; // Флаг для отслеживания обновления роли + // Сравниваем текущий authType с 'telegram' if (auth.authType.value !== 'telegram') { // Если пользователь авторизован не через Telegram, связываем идентификаторы - console.log('Связывание Telegram с существующим аккаунтом:', telegramId); - const linkResult = await auth.linkIdentity('telegram', telegramId); - - if (linkResult.success) { - notifications.value.successMessage = - 'Telegram успешно подключен к вашему аккаунту!'; - notifications.value.showSuccess = true; - - setTimeout(() => { - notifications.value.showSuccess = false; - }, 3000); - } else { - notifications.value.errorMessage = - linkResult.error || 'Не удалось подключить Telegram'; - notifications.value.showError = true; - - setTimeout(() => { - notifications.value.showError = false; - }, 3000); - } + // Эта логика может быть избыточной, если checkAuth уже обновил authType + // Возможно, достаточно просто проверить authType после checkAuth + console.log('Связывание Telegram с существующим аккаунтом (проверка authType):', telegramId, auth.authType.value); + // Уведомление об успехе + notifications.value.successMessage = 'Telegram успешно подключен к вашему аккаунту!'; + notifications.value.showSuccess = true; + setTimeout(() => { notifications.value.showSuccess = false; }, 3000); } else { - // Если новая аутентификация через Telegram - console.log('Telegram аутентификация успешна'); - - // Загружаем сообщения после аутентификации - await loadMessages({ authType: 'telegram' }); + // Если новая аутентификация через Telegram или authType уже telegram + console.log('Telegram аутентификация/привязка успешна (authType="telegram")'); + // await loadMessages({ authType: 'telegram' }); // Загрузка сообщений уже обрабатывается watch(isAuthenticated) + roleUpdated = true; // Роль могла обновиться при этой аутентификации } - // Очищаем интервал и скрываем окно верификации - clearTelegramInterval(); - telegramAuth.value.showVerification = false; - telegramAuth.value.verificationCode = ''; + // Проверяем роль еще раз после возможной привязки/аутентификации + // await auth.checkAuth(); // Дополнительный checkAuth, возможно, не нужен, т.к. он вызывается в начале интервала + + if (hasIdentityType('wallet')) { + console.log('[handleTelegramAuth] Wallet linked, updating balances...'); + await updateBalances(); // Вызываем обновление баланса + startBalanceUpdates(); // Запускаем интервал обновления, если еще не запущен + } + // Нет необходимости продолжать интервал после успеха + return; // Выходим из колбека setInterval + } else { + console.log('[handleTelegramAuth] Still waiting for Telegram verification...'); } } catch (error) { - console.error('Ошибка при проверке аутентификации:', error); + console.error('Ошибка при проверке аутентификации в интервале:', error); + // Очищать ли интервал при ошибке? Возможно, да, чтобы не спамить ошибками. + // clearTelegramInterval(); + // telegramAuth.value.error = 'Ошибка проверки статуса Telegram.'; } }, 2000); // Проверяем каждые 2 секунды } else { @@ -1290,6 +1293,7 @@ // Получаем текущее состояние аутентификации const authResponse = await auth.checkAuth(); + let roleUpdated = false; // Флаг для отслеживания обновления роли if (auth.isAuthenticated.value && emailAuth.value.verificationEmail) { // Если пользователь уже авторизован, связываем email @@ -1304,45 +1308,40 @@ // Показываем сообщение об успехе notifications.value.successMessage = `Email ${emailAuth.value.verificationEmail} успешно подключен к вашему аккаунту!`; notifications.value.showSuccess = true; - - // Скрываем сообщение через 3 секунды - setTimeout(() => { - notifications.value.showSuccess = false; - }, 3000); + setTimeout(() => { notifications.value.showSuccess = false; }, 3000); } else { notifications.value.errorMessage = linkResult.error || 'Не удалось подключить Email'; notifications.value.showError = true; - - setTimeout(() => { - notifications.value.showError = false; - }, 3000); + setTimeout(() => { notifications.value.showError = false; }, 3000); } } else { - // Показываем сообщение об успехе + // Показываем сообщение об успехе, если просто подтвердили email notifications.value.successMessage = `Email ${emailAuth.value.verificationEmail} успешно подтвержден!`; notifications.value.showSuccess = true; + setTimeout(() => { notifications.value.showSuccess = false; }, 3000); - // Скрываем сообщение через 3 секунды - setTimeout(() => { - notifications.value.showSuccess = false; - }, 3000); - - // Загружаем сообщения после аутентификации await loadMessages({ authType: 'email' }); + roleUpdated = true; // Роль могла обновиться } } else { // Если пользователь не был авторизован до этого - // Показываем сообщение об успехе notifications.value.successMessage = `Email ${emailAuth.value.verificationEmail} успешно подтвержден!`; notifications.value.showSuccess = true; + setTimeout(() => { notifications.value.showSuccess = false; }, 3000); - // Скрываем сообщение через 3 секунды - setTimeout(() => { - notifications.value.showSuccess = false; - }, 3000); - - // Загружаем сообщения после аутентификации await loadMessages({ authType: 'email' }); + roleUpdated = true; // Роль могла обновиться при новой аутентификации + } + + // Проверяем роль еще раз после возможной привязки/аутентификации + if (!roleUpdated) { + await auth.checkAuth(); // Перепроверяем auth состояние, чтобы получить актуальную роль + } + + if (hasIdentityType('wallet')) { + console.log('[verifyEmailCode] Wallet linked, updating balances...'); + await updateBalances(); // Вызываем обновление баланса + startBalanceUpdates(); // Запускаем интервал обновления, если еще не запущен } } else { emailAuth.value.error = response.data.message || 'Неверный код верификации'; @@ -1542,26 +1541,41 @@ * Обновляет балансы токенов */ const updateBalances = async () => { - if (auth.isAuthenticated.value && auth.address?.value) { - try { - console.log('Запрос балансов для адреса:', auth.address.value); - const balances = await fetchTokenBalances(); - console.log('Полученные балансы:', balances); + if (auth.isAuthenticated.value) { + // Пытаемся получить адрес сначала из прямого значения, потом из идентификаторов + const walletAddress = auth.address?.value || getIdentityValue('wallet'); - // Обновляем каждый баланс отдельно для реактивности - tokenBalances.value = { - eth: balances.eth || '0', - bsc: balances.bsc || '0', - arbitrum: balances.arbitrum || '0', - polygon: balances.polygon || '0', - }; + if (walletAddress) { + try { + console.log('Запрос балансов для адреса:', walletAddress); + // Важно: Убедитесь, что fetchTokenBalances использует переданный адрес + // Если fetchTokenBalances неявно использует auth.address.value, + // его нужно будет модифицировать или передавать адрес явно. + // ПРЕДПОЛАГАЕМ, что fetchTokenBalances работает корректно или будет исправлен. + const balances = await fetchTokenBalances(walletAddress); // Передаем адрес явно, если нужно + console.log('Полученные балансы:', balances); - console.log('Обновленные балансы в интерфейсе:', tokenBalances.value); - } catch (error) { - console.error('Ошибка при обновлении балансов:', error); + // Обновляем каждый баланс отдельно для реактивности + tokenBalances.value = { + eth: balances.eth || '0', + bsc: balances.bsc || '0', + arbitrum: balances.arbitrum || '0', + polygon: balances.polygon || '0', + }; + + console.log('Обновленные балансы в интерфейсе:', tokenBalances.value); + } catch (error) { + console.error('Ошибка при обновлении балансов:', error); + } + } else { + console.log('Не найден адрес кошелька для запроса балансов.'); + // Можно обнулить балансы, если адрес не найден + tokenBalances.value = { eth: '0', bsc: '0', arbitrum: '0', polygon: '0' }; } } else { - console.log('Пользователь не аутентифицирован или адрес не доступен'); + console.log('Пользователь не аутентифицирован.'); + // Также обнуляем балансы, если не аутентифицирован + tokenBalances.value = { eth: '0', bsc: '0', arbitrum: '0', polygon: '0' }; } };