ваше сообщение коммита

This commit is contained in:
2025-04-15 19:30:56 +03:00
parent 3ceefd046e
commit 4330a1f2a0
14 changed files with 3234 additions and 1529 deletions

View File

@@ -397,6 +397,28 @@ class AuthService {
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
@@ -406,9 +428,6 @@ class AuthService {
[telegramId]
);
let userId;
let isNewUser = false;
// Если пользователь существует с таким telegramId, используем его
if (existingUserResult.rows.length > 0) {
const existingUser = existingUserResult.rows[0];
@@ -458,9 +477,8 @@ class AuthService {
async checkAdminTokens(address) {
if (!address) return false;
console.log(`Checking admin tokens for address: ${address}`);
logger.info(`Checking admin tokens for address: ${address}`);
const isAdmin = await this.checkAdminRole(address);
console.log(`Admin token check result for ${address}: ${isAdmin}`);
// Обновляем роль пользователя в базе данных, если есть админские токены
if (isAdmin) {
@@ -480,10 +498,10 @@ class AuthService {
'UPDATE users SET role = $1 WHERE id = $2',
['admin', userId]
);
console.log(`Updated user ${userId} role to admin based on token holdings`);
logger.info(`Updated user ${userId} role to admin based on token holdings`);
}
} catch (error) {
console.error('Error updating user role:', error);
logger.error('Error updating user role:', error);
}
}
@@ -564,6 +582,79 @@ class AuthService {
}
}
/**
* Связывает новый идентификатор с существующим пользователем
* @param {number} userId - ID пользователя
* @param {string} provider - Тип идентификатора (wallet, email, telegram)
* @param {string} providerId - Значение идентификатора
* @returns {Promise<Object>} - Результат операции
*/
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');
}
// Нормализуем значение идентификатора
if (provider === 'wallet' && providerId) {
providerId = providerId.toLowerCase();
} else if (provider === 'email' && providerId) {
providerId = providerId.toLowerCase();
}
logger.info(`[AuthService] Linking identity ${provider}:${providerId} to user ${userId}`);
// Проверяем, существует ли уже такой идентификатор
const existingResult = await db.query(
`SELECT user_id FROM user_identities WHERE provider = $1 AND provider_id = $2`,
[provider, providerId]
);
if (existingResult.rows.length > 0) {
const existingUserId = existingResult.rows[0].user_id;
// Если идентификатор уже принадлежит этому пользователю, ничего не делаем
if (existingUserId === userId) {
logger.info(`[AuthService] Identity ${provider}:${providerId} already exists for user ${userId}`);
return { success: true, message: 'Identity already exists' };
} else {
// Если идентификатор принадлежит другому пользователю, возвращаем ошибку
logger.warn(`[AuthService] Identity ${provider}:${providerId} 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, providerId]
);
// Проверяем и обновляем роль администратора, если это идентификатор кошелька
let isAdmin = false;
if (provider === 'wallet') {
isAdmin = await this.checkAdminTokens(providerId);
// Обновляем роль пользователя в базе данных, если нужно
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}:${providerId} 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;
}
}
/**
* Обработка гостевых сообщений после аутентификации
* ПРИМЕЧАНИЕ: Эта функция оставлена для обратной совместимости.

View File

@@ -16,18 +16,34 @@ class EmailAuth {
throw new Error('Некорректный формат email');
}
// Проверяем, существует ли пользователь с таким email
const existingEmailUser = await db.query(
`SELECT u.id FROM users u
JOIN user_identities i ON u.id = i.user_id
WHERE i.provider = 'email' AND i.provider_id = $1`,
[email.toLowerCase()]
);
// Создаем или получаем ID пользователя
let userId;
if (session.authenticated && session.userId) {
// Если пользователь уже аутентифицирован, используем его ID
userId = session.userId;
logger.info(`[initEmailAuth] Using existing authenticated user ${userId} for email ${email}`);
} else if (existingEmailUser.rows.length > 0) {
// Если найден пользователь с таким email, используем его ID
userId = existingEmailUser.rows[0].id;
logger.info(`[initEmailAuth] Found existing user ${userId} with email ${email}`);
} else {
// Создаем временного пользователя, если нужно будет создать нового
const userResult = await db.query(
'INSERT INTO users (role) VALUES ($1) RETURNING id',
['user']
);
userId = userResult.rows[0].id;
session.tempUserId = userId;
logger.info(`[initEmailAuth] Created temporary user ${userId} for email ${email}`);
}
// Сохраняем email в сессии
@@ -73,7 +89,25 @@ class EmailAuth {
const email = session.pendingEmail.toLowerCase();
let finalUserId;
// Ищем всех пользователей с похожими идентификаторами
// Если пользователь уже авторизован, используем его ID
if (session.authenticated && session.userId) {
finalUserId = session.userId;
logger.info(`[checkEmailVerification] Using existing authenticated user ${finalUserId}`);
// Связываем email с существующим пользователем
await authService.linkIdentity(finalUserId, 'email', email);
// Очищаем временные данные
delete session.pendingEmail;
return {
verified: true,
userId: finalUserId,
email: email
};
}
// Если пользователь не авторизован, ищем всех пользователей с похожими идентификаторами
const identities = {
email: email,
guest: session.guestId

View File

@@ -8,7 +8,7 @@ class IdentityService {
/**
* Сохраняет идентификатор пользователя в базу данных
* @param {number} userId - ID пользователя
* @param {string} provider - Тип идентификатора (wallet, email, telegram, guest)
* @param {string} provider - Тип идентификатора (wallet, email, telegram)
* @param {string} providerId - Значение идентификатора
* @param {boolean} verified - Флаг верификации идентификатора
* @returns {Promise<object>} - Результат операции
@@ -23,6 +23,38 @@ class IdentityService {
};
}
// Приводим provider и providerId к нужному формату
provider = provider.toLowerCase();
if (provider === 'wallet' || provider === 'email') {
providerId = providerId.toLowerCase();
}
// Проверяем тип провайдера и перенаправляем гостевые идентификаторы в guest_user_mapping
if (provider === 'guest') {
logger.info(`[IdentityService] Converting guest identity for user ${userId} to guest_user_mapping: ${providerId}`);
try {
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, providerId]
);
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(provider)) {
logger.warn(`[IdentityService] Invalid provider type: ${provider}`);
return {
success: false,
error: `Invalid provider type. Allowed types: ${allowedProviders.join(', ')}`
};
}
logger.info(`[IdentityService] Saving identity for user ${userId}: ${provider}:${providerId}`);
// Проверяем, существует ли уже такой идентификатор
@@ -177,15 +209,31 @@ class IdentityService {
results.push({ type: 'telegram', result: telegramResult });
}
// Сохраняем гостевые идентификаторы
// Сохраняем гостевые идентификаторы в guest_user_mapping
if (session.guestId) {
const guestResult = await this.saveIdentity(userId, 'guest', session.guestId, true);
results.push({ type: 'guest', result: guestResult });
try {
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]
);
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) {
const prevGuestResult = await this.saveIdentity(userId, 'guest', session.previousGuestId, true);
results.push({ type: 'previousGuest', result: prevGuestResult });
try {
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.previousGuestId]
);
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}`);
@@ -223,12 +271,42 @@ class IdentityService {
// Переносим каждый идентификатор
for (const identity of identitiesResult.rows) {
await client.query(
`UPDATE user_identities
SET user_id = $1
WHERE user_id = $2 AND provider = $3 AND provider_id = $4`,
[toUserId, fromUserId, identity.provider, identity.provider_id]
`INSERT INTO user_identities (user_id, provider, provider_id)
VALUES ($1, $2, $3)
ON CONFLICT (provider, provider_id) DO NOTHING`,
[toUserId, identity.provider, identity.provider_id]
);
// Удаляем старый идентификатор
await client.query(
`DELETE FROM user_identities
WHERE user_id = $1 AND provider = $2 AND provider_id = $3`,
[fromUserId, identity.provider, identity.provider_id]
);
}
// Мигрируем гостевые идентификаторы из новой таблицы guest_user_mapping
const guestMappingsResult = await client.query(
`SELECT guest_id, processed FROM guest_user_mapping WHERE user_id = $1`,
[fromUserId]
);
// Переносим каждый гостевой идентификатор
for (const mapping of guestMappingsResult.rows) {
await client.query(
`INSERT INTO guest_user_mapping (user_id, guest_id, processed)
VALUES ($1, $2, $3)
ON CONFLICT (guest_id) DO UPDATE
SET user_id = $1, processed = EXCLUDED.processed OR guest_user_mapping.processed`,
[toUserId, mapping.guest_id, mapping.processed]
);
}
// Удаляем старые гостевые маппинги
await client.query(
`DELETE FROM guest_user_mapping WHERE user_id = $1`,
[fromUserId]
);
// Переносим все сообщения
await client.query(
@@ -245,28 +323,29 @@ class IdentityService {
WHERE user_id = $2`,
[toUserId, fromUserId]
);
// Удаляем исходного пользователя
// Переносим настройки пользователя
await client.query(
`DELETE FROM users WHERE id = $1`,
[fromUserId]
`UPDATE user_preferences
SET user_id = $1
WHERE user_id = $2`,
[toUserId, fromUserId]
);
// Завершаем транзакцию
await client.query('COMMIT');
logger.info(`[IdentityService] Successfully migrated data from user ${fromUserId} to user ${toUserId}`);
return {
success: true,
migratedIdentities: identitiesResult.rows.length
};
logger.info(`[IdentityService] Successfully migrated data from user ${fromUserId} to ${toUserId}`);
return { success: true };
} catch (error) {
await client.query('ROLLBACK');
throw error;
logger.error(`[IdentityService] Transaction error:`, error);
return { success: false, error: error.message };
} finally {
client.release();
}
} catch (error) {
logger.error(`[IdentityService] Error migrating data from user ${fromUserId} to user ${toUserId}:`, error);
logger.error(`[IdentityService] Error migrating user data:`, error);
return { success: false, error: error.message };
}
}

View File

@@ -1,40 +1,167 @@
const logger = require('../utils/logger');
const db = require('../db');
const { processGuestMessages } = require('../routes/chat');
/**
* Сервис для работы с сессиями пользователей
*/
class SessionService {
/**
* Сохраняет сессию с обработкой ошибок
* @param {object} session - Объект сессии
* @param {string} context - Контекст для логирования
* @returns {Promise<boolean>} - Результат операции
* Сохраняет сессию, обрабатывая ошибки и логируя результат
* @param {Object} session - Объект сессии Express
* @returns {Promise<boolean>} - Успешно ли сохранена сессия
*/
async saveSession(session, context = '') {
if (!session) {
logger.warn(`[SessionService${context ? '/' + context : ''}] Cannot save null session`);
return false;
}
async saveSession(session) {
try {
return await new Promise((resolve, reject) => {
return new Promise((resolve, reject) => {
session.save(err => {
if (err) {
logger.error(`[SessionService${context ? '/' + context : ''}] Error saving session:`, err);
logger.error('Error saving session:', err);
reject(err);
} else {
logger.info(`[SessionService${context ? '/' + context : ''}] Session saved successfully`);
logger.info('Session saved successfully');
resolve(true);
}
});
});
} catch (error) {
logger.error(`[SessionService${context ? '/' + context : ''}] Error in saveSession:`, error);
return false;
logger.error(`[saveSession] Error:`, error);
throw error;
}
}
/**
* Связывает гостевые сообщения с пользователем после аутентификации
* @param {Object} session - Объект сессии Express
* @param {number} userId - ID пользователя
* @returns {Promise<Object>} - Результат операции
*/
async linkGuestMessages(session, userId) {
try {
logger.info(`[linkGuestMessages] Starting for user ${userId} with guestId=${session.guestId}, previousGuestId=${session.previousGuestId}`);
// Инициализируем массив обработанных гостевых ID, если его нет
if (!session.processedGuestIds) {
session.processedGuestIds = [];
}
// Получаем все гостевые ID для текущего пользователя из новой таблицы
const guestIdsResult = await db.query(
'SELECT guest_id FROM guest_user_mapping WHERE user_id = $1',
[userId]
);
const userGuestIds = guestIdsResult.rows.map(row => row.guest_id);
// Собираем все гостевые ID, которые нужно обработать
const guestIdsToProcess = new Set();
// Добавляем текущий гостевой ID
if (session.guestId && !session.processedGuestIds.includes(session.guestId)) {
guestIdsToProcess.add(session.guestId);
// Записываем связь с пользователем в новую таблицу
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]
);
}
// Добавляем предыдущий гостевой ID
if (session.previousGuestId && !session.processedGuestIds.includes(session.previousGuestId)) {
guestIdsToProcess.add(session.previousGuestId);
// Записываем связь с пользователем в новую таблицу
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.previousGuestId]
);
}
// Добавляем все гостевые ID пользователя из таблицы
for (const guestId of userGuestIds) {
if (!session.processedGuestIds.includes(guestId)) {
guestIdsToProcess.add(guestId);
}
}
// Обрабатываем все собранные гостевые ID
for (const guestId of guestIdsToProcess) {
await this.processGuestMessagesWrapper(userId, guestId);
session.processedGuestIds.push(guestId);
// Помечаем guestId как обработанный в базе данных
await db.query(
'UPDATE guest_user_mapping SET processed = true WHERE guest_id = $1',
[guestId]
);
}
// Сохраняем сессию
await this.saveSession(session);
return { success: true };
} catch (error) {
logger.error('[linkGuestMessages] Error:', error);
return { success: false, error: error.message };
}
}
/**
* Обертка для функции processGuestMessages
* @param {number} userId - ID пользователя
* @param {string} guestId - ID гостя
* @returns {Promise<Object>} - Результат операции
*/
async processGuestMessagesWrapper(userId, guestId) {
try {
logger.info(`[processGuestMessagesWrapper] Processing messages: userId=${userId}, guestId=${guestId}`);
return await processGuestMessages(userId, guestId);
} catch (error) {
logger.error(`[processGuestMessagesWrapper] Error: ${error.message}`, error);
throw error;
}
}
/**
* Получает сессию из хранилища по ID
* @param {string} sessionId - ID сессии
* @returns {Promise<Object|null>} - Объект сессии или null
*/
async getSession(sessionId) {
try {
// Реализация будет зависеть от используемого хранилища сессий
// Этот метод будет полезен, если нужно получить сессию не из текущего запроса
return null; // Временная заглушка
} catch (error) {
logger.error(`[getSession] Error getting session ${sessionId}:`, error);
return null;
}
}
/**
* Удаляет сессию
* @param {Object} session - Объект сессии Express
* @returns {Promise<boolean>} - Успешно ли удалена сессия
*/
async destroySession(session) {
try {
return new Promise((resolve, reject) => {
session.destroy(err => {
if (err) {
logger.error('Error destroying session:', err);
reject(err);
} else {
logger.info('Session destroyed successfully');
resolve(true);
}
});
});
} catch (error) {
logger.error(`[destroySession] Error:`, error);
throw error;
}
}
/**
* Восстанавливает сессию из базы данных по ID
* @param {string} sessionId - ID сессии
@@ -166,4 +293,5 @@ class SessionService {
}
}
module.exports = new SessionService();
const sessionService = new SessionService();
module.exports = sessionService;