/** * Copyright (c) 2024-2025 Тарабанов Александр Викторович * All rights reserved. * * This software is proprietary and confidential. * Unauthorized copying, modification, or distribution is prohibited. * * For licensing inquiries: info@hb3-accelerator.com * Website: https://hb3-accelerator.com * GitHub: https://github.com/HB3-ACCELERATOR */ // console.log('[identity-service] loaded'); const encryptedDb = require('./encryptedDatabaseService'); const db = require('../db'); const logger = require('../utils/logger'); const { getLinkedWallet } = require('./wallet-service'); const { checkAdminRole } = require('./admin-role'); const { broadcastContactsUpdate } = require('../wsHub'); /** * Сервис для работы с идентификаторами пользователей */ class IdentityService { /** * Нормализует значения идентификаторов (приводит к нижнему регистру где нужно) * @param {string} provider - Тип идентификатора * @param {string} providerId - Значение идентификатора * @returns {object} - Нормализованные значения */ normalizeIdentity(provider, providerId) { if (!provider || !providerId) { return { provider, providerId }; } // Приводим провайдер к нижнему регистру const normalizedProvider = provider.toLowerCase(); // Для email и wallet приводим значение к нижнему регистру let normalizedProviderId = providerId; if (normalizedProvider === 'wallet' || normalizedProvider === 'email') { normalizedProviderId = providerId.toLowerCase(); } return { provider: normalizedProvider, providerId: normalizedProviderId, }; } /** * Сохраняет идентификатор пользователя в базу данных * @param {number} userId - ID пользователя * @param {string} provider - Тип идентификатора (wallet, email, telegram) * @param {string} providerId - Значение идентификатора * @param {boolean} verified - Флаг верификации идентификатора (не используется в БД) * @returns {Promise} - Результат операции */ async saveIdentity(userId, provider, providerId, verified = true) { try { if (!userId || !provider || !providerId) { logger.warn( `[IdentityService] Missing required parameters: userId=${userId}, provider=${provider}, providerId=${providerId}` ); return { success: false, error: 'Missing required parameters', }; } // Нормализуем значения const { provider: normalizedProvider, providerId: normalizedProviderId } = this.normalizeIdentity(provider, providerId); // Проверяем тип провайдера и перенаправляем гостевые идентификаторы в unified_guest_mapping if (normalizedProvider === 'guest') { logger.info( `[IdentityService] Converting guest identity for user ${userId} to unified_guest_mapping: ${normalizedProviderId}` ); try { const db = require('../db'); const encryptionUtils = require('../utils/encryptionUtils'); const encryptionKey = encryptionUtils.getEncryptionKey(); await db.getQuery()( `INSERT INTO unified_guest_mapping (user_id, identifier_encrypted, channel, created_at) VALUES ($1, encrypt_text($2, $4), $3, NOW()) ON CONFLICT (identifier_encrypted, channel) DO NOTHING`, [userId, `web:${normalizedProviderId}`, 'web', encryptionKey] ); return { success: true }; } catch (guestError) { logger.error( `[IdentityService] Error saving guest identity for user ${userId}:`, guestError ); return { success: false, error: guestError.message }; } } // Проверяем, разрешен ли такой тип провайдера const allowedProviders = ['email', 'wallet', 'telegram', 'username']; if (!allowedProviders.includes(normalizedProvider)) { logger.warn(`[IdentityService] Invalid provider type: ${normalizedProvider}`); return { success: false, error: `Invalid provider type: ${normalizedProvider}`, }; } // Проверяем, существует ли уже такой идентификатор const existingIdentity = await this.findIdentity(userId, normalizedProvider); if (existingIdentity) { // Обновляем существующий идентификатор await encryptedDb.saveData('user_identities', { provider: normalizedProvider, provider_id: normalizedProviderId }, { user_id: userId, provider: normalizedProvider }); logger.info( `[IdentityService] Updated identity for user ${userId}: ${normalizedProvider}=${normalizedProviderId}` ); } else { // Создаем новый идентификатор await encryptedDb.saveData('user_identities', { user_id: userId, provider: normalizedProvider, provider_id: normalizedProviderId }); logger.info( `[IdentityService] Saved new identity for user ${userId}: ${normalizedProvider}=${normalizedProviderId}` ); } return { success: true }; } catch (error) { logger.error( `[IdentityService] Error saving identity for user ${userId}:`, error ); return { success: false, error: error.message }; } } /** * Получает все идентификаторы пользователя * @param {number} userId - ID пользователя * @returns {Promise} - Массив идентификаторов */ async getUserIdentities(userId) { try { const identities = await encryptedDb.getData('user_identities', { user_id: userId }); logger.info(`[IdentityService] Found ${identities.length} identities for user ${userId}`); // Данные уже расшифрованы encryptedDb, просто переименовываем поля const formattedIdentities = identities.map(identity => ({ id: identity.id, user_id: identity.user_id, created_at: identity.created_at, provider: identity.provider, // Уже расшифровано provider_id: identity.provider_id // Уже расшифровано })); return formattedIdentities; } catch (error) { logger.error(`[IdentityService] Error getting identities for user ${userId}:`, error); // Если не удалось получить данные через encryptedDb, пробуем через обычный запрос // logger.info(`[IdentityService] Trying to get unencrypted data for user ${userId}`); // Убрано избыточное логирование const { rows } = await db.getQuery()(` SELECT provider, provider_id, provider_username, provider_avatar, created_at, updated_at FROM user_identities WHERE user_id = $1 ORDER BY created_at DESC `, [userId]); // logger.info(`[IdentityService] Found ${rows.length} unencrypted identities for user ${userId}`); // Убрано избыточное логирование return rows; } } /** * Получает идентификаторы пользователя по типу провайдера * @param {number} userId - ID пользователя * @param {string} provider - Тип провайдера * @returns {Promise} - Массив идентификаторов */ async getUserIdentitiesByProvider(userId, provider) { try { const identities = await encryptedDb.getData('user_identities', { user_id: userId, provider: provider.toLowerCase() }); return identities; } catch (error) { logger.error(`[IdentityService] Error getting identities by provider for user ${userId}:`, error); return []; } } /** * Находит пользователя по идентификатору * @param {string} provider - Тип провайдера * @param {string} providerId - Значение идентификатора * @returns {Promise} - Пользователь или null */ async findUserByIdentity(provider, providerId) { try { const { provider: normalizedProvider, providerId: normalizedProviderId } = this.normalizeIdentity(provider, providerId); const identities = await encryptedDb.getData('user_identities', { provider: normalizedProvider, provider_id: normalizedProviderId }, 1); if (identities.length === 0) { return null; } const userId = identities[0].user_id; const users = await encryptedDb.getData('users', { id: userId }, 1); return users.length > 0 ? users[0] : null; } catch (error) { logger.error(`[IdentityService] Error finding user by identity:`, error); return null; } } /** * Находит конкретный идентификатор пользователя * @param {number} userId - ID пользователя * @param {string} provider - Тип провайдера * @returns {Promise} - Идентификатор или null */ async findIdentity(userId, provider) { try { const identities = await encryptedDb.getData('user_identities', { user_id: userId, provider: provider.toLowerCase() }, 1); return identities.length > 0 ? identities[0] : null; } catch (error) { logger.error(`[IdentityService] Error finding identity for user ${userId}:`, error); return null; } } /** * Сохраняет идентификаторы из сессии для пользователя * @param {object} session - Объект сессии * @param {number} userId - ID пользователя * @returns {Promise} - Результат операции */ async saveIdentitiesFromSession(session, userId) { try { if (!session || !userId) { logger.warn(`[IdentityService] Missing parameters: session=${!!session}, userId=${userId}`); return { success: false, error: 'Missing required parameters' }; } const results = []; // Сохраняем все постоянные идентификаторы из сессии if (session.email) { const emailResult = await this.saveIdentity(userId, 'email', session.email, true); results.push({ type: 'email', result: emailResult }); } if (session.address) { const walletResult = await this.saveIdentity(userId, 'wallet', session.address, true); results.push({ type: 'wallet', result: walletResult }); } if (session.telegramId) { const telegramResult = await this.saveIdentity( userId, 'telegram', session.telegramId, true ); results.push({ type: 'telegram', result: telegramResult }); } // Сохраняем гостевые идентификаторы в unified_guest_mapping if (session.guestId) { try { const db = require('../db'); const encryptionUtils = require('../utils/encryptionUtils'); const encryptionKey = encryptionUtils.getEncryptionKey(); await db.getQuery()( `INSERT INTO unified_guest_mapping (user_id, identifier_encrypted, channel, created_at) VALUES ($1, encrypt_text($2, $4), $3, NOW()) ON CONFLICT (identifier_encrypted, channel) DO NOTHING`, [userId, `web:${session.guestId}`, 'web', encryptionKey] ); results.push({ type: 'guest', result: { success: true } }); } catch (error) { logger.error(`[IdentityService] Error saving guest ID for user ${userId}:`, error); results.push({ type: 'guest', result: { success: false, error: error.message } }); } } if (session.previousGuestId && session.previousGuestId !== session.guestId) { try { const db = require('../db'); const encryptionUtils = require('../utils/encryptionUtils'); const encryptionKey = encryptionUtils.getEncryptionKey(); await db.getQuery()( `INSERT INTO unified_guest_mapping (user_id, identifier_encrypted, channel, created_at) VALUES ($1, encrypt_text($2, $4), $3, NOW()) ON CONFLICT (identifier_encrypted, channel) DO NOTHING`, [userId, `web:${session.previousGuestId}`, 'web', encryptionKey] ); results.push({ type: 'previousGuest', result: { success: true } }); } catch (error) { logger.error( `[IdentityService] Error saving previous guest ID for user ${userId}:`, error ); results.push({ type: 'previousGuest', result: { success: false, error: error.message } }); } } logger.info( `[IdentityService] Saved ${results.length} identities from session for user ${userId}` ); return { success: true, results }; } catch (error) { logger.error( `[IdentityService] Error saving identities from session for user ${userId}:`, error ); return { success: false, error: error.message }; } } /** * Мигрирует все идентификаторы и сообщения от одного пользователя к другому * @param {number} fromUserId - ID исходного пользователя * @param {number} toUserId - ID целевого пользователя * @returns {Promise} - Результат операции */ async migrateUserData(fromUserId, toUserId) { try { if (!fromUserId || !toUserId) { logger.warn( `[IdentityService] Missing parameters: fromUserId=${fromUserId}, toUserId=${toUserId}` ); return { success: false, error: 'Missing required parameters' }; } // Получаем все идентификаторы исходного пользователя const identities = await encryptedDb.getData('user_identities', { user_id: fromUserId }); // Переносим каждый идентификатор for (const identity of identities) { // Создаем новый идентификатор для целевого пользователя await encryptedDb.saveData('user_identities', { user_id: toUserId, provider: identity.provider, provider_id: identity.provider_id }); // Удаляем старый идентификатор await encryptedDb.deleteData('user_identities', { user_id: fromUserId, provider: identity.provider, provider_id: identity.provider_id }); } // Мигрируем гостевые идентификаторы const guestMappings = await encryptedDb.getData('unified_guest_mapping', { user_id: fromUserId }); // Переносим каждый гостевой идентификатор for (const mapping of guestMappings) { const db = require('../db'); const encryptionUtils = require('../utils/encryptionUtils'); const encryptionKey = encryptionUtils.getEncryptionKey(); await db.getQuery()( `INSERT INTO unified_guest_mapping (user_id, identifier_encrypted, channel, processed, processed_at, created_at) VALUES ($1, encrypt_text($2, $6), $3, $4, $5, NOW()) ON CONFLICT (identifier_encrypted, channel) DO UPDATE SET user_id = $1, processed = $4, processed_at = $5`, [toUserId, mapping.identifier_encrypted, mapping.channel, mapping.processed, mapping.processed_at, encryptionKey] ); } // Удаляем старые гостевые маппинги await encryptedDb.deleteData('unified_guest_mapping', { user_id: fromUserId }); // Переносим все сообщения const messages = await encryptedDb.getData('messages', { user_id: fromUserId }); for (const message of messages) { await encryptedDb.saveData('messages', { ...message, user_id: toUserId }); await encryptedDb.deleteData('messages', { id: message.id }); } // Переносим все диалоги const conversations = await encryptedDb.getData('conversations', { user_id: fromUserId }); for (const conversation of conversations) { await encryptedDb.saveData('conversations', { ...conversation, user_id: toUserId }); await encryptedDb.deleteData('conversations', { id: conversation.id }); } // Переносим настройки пользователя const preferences = await encryptedDb.getData('user_preferences', { user_id: fromUserId }); for (const preference of preferences) { await encryptedDb.saveData('user_preferences', { ...preference, user_id: toUserId }); await encryptedDb.deleteData('user_preferences', { id: preference.id }); } logger.info( `[IdentityService] Successfully migrated data from user ${fromUserId} to ${toUserId}` ); return { success: true }; } catch (error) { logger.error(`[IdentityService] Error migrating user data:`, error); return { success: false, error: error.message }; } } /** * Находит всех пользователей с похожими идентификаторами * @param {object} identities - Объект с идентификаторами * @returns {Promise} - Массив ID пользователей */ async findRelatedUsers(identities) { try { const userIds = new Set(); for (const [provider, providerId] of Object.entries(identities)) { if (!providerId) continue; const users = await encryptedDb.getData('user_identities', { provider: provider, provider_id: providerId }); users.forEach((user) => userIds.add(user.user_id)); } return Array.from(userIds); } catch (error) { logger.error(`[IdentityService] Error finding related users:`, error); return []; } } /** * Удаляет идентификатор пользователя * @param {number} userId - ID пользователя * @param {string} provider - Тип идентификатора * @param {string} providerId - Значение идентификатора * @returns {Promise} - Результат операции */ async deleteIdentity(userId, provider, providerId) { try { if (!userId || !provider || !providerId) { logger.warn(`[IdentityService] Missing parameters for deleteIdentity: userId=${userId}, provider=${provider}, providerId=${providerId}`); return { success: false, error: 'Missing required parameters' }; } const { provider: normalizedProvider, providerId: normalizedProviderId } = this.normalizeIdentity(provider, providerId); const result = await encryptedDb.deleteData('user_identities', { user_id: userId, provider: normalizedProvider, provider_id: normalizedProviderId }); logger.info(`[IdentityService] Deleted identity ${normalizedProvider}:${normalizedProviderId} for user ${userId}`); return { success: true, deleted: result.length }; } catch (error) { logger.error(`[IdentityService] Error deleting identity ${provider}:${providerId} for user ${userId}:`, error); return { success: false, error: error.message }; } } /** * Универсальная функция: найти или создать пользователя по идентификатору, привязать идентификатор, проверить роль * @param {string} provider - Тип идентификатора ('email' | 'telegram') * @param {string} providerId - Значение идентификатора * @param {object} [options] - Дополнительные опции * @returns {Promise<{userId: number, role: string, isNew: boolean}>} */ async findOrCreateUserWithRole(provider, providerId, options = {}) { let user = await this.findUserByIdentity(provider, providerId); let isNew = false; if (!user) { // Создаем пользователя const newUser = await encryptedDb.saveData('users', { role: 'user' }); const userId = newUser.id; await this.saveIdentity(userId, provider, providerId, true); user = { id: userId, role: 'user' }; isNew = true; logger.info('[WS] broadcastContactsUpdate after new user created'); broadcastContactsUpdate(); } // Проверяем связь с кошельком const wallet = await getLinkedWallet(user.id); let role = 'user'; if (wallet) { const userAccessLevel = await authService.getUserAccessLevel(wallet); role = userAccessLevel.hasAccess ? 'admin' : 'user'; // Обновляем роль в users, если изменилась if (user.role !== role) { await encryptedDb.saveData('users', { role: role }, { id: user.id }); } } return { userId: user.id, role, isNew }; } } module.exports = new IdentityService();