ваше сообщение коммита
This commit is contained in:
@@ -534,51 +534,133 @@ router.post('/link-identity', async (req, res) => {
|
|||||||
// Проверка статуса аутентификации
|
// Проверка статуса аутентификации
|
||||||
router.get('/check', async (req, res) => {
|
router.get('/check', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
console.log('Check auth, session data:', {
|
logger.info(`[session/check] Checking session: ${req.sessionID}`);
|
||||||
userId: req.session.userId,
|
|
||||||
authenticated: req.session.authenticated,
|
|
||||||
authType: req.session.authType,
|
|
||||||
telegramId: req.session.telegramId
|
|
||||||
});
|
|
||||||
|
|
||||||
// Если пользователь не аутентифицирован
|
const authenticated = req.session.authenticated || false;
|
||||||
if (!req.session.authenticated || !req.session.userId) {
|
const authType = req.session.authType || null;
|
||||||
return res.json({
|
|
||||||
authenticated: false
|
// Подробное логирование для отладки восстановления сессии
|
||||||
|
logger.info(`[session/check] Session state: authenticated=${authenticated}, authType=${authType}, userId=${req.session.userId || 'none'}`);
|
||||||
|
|
||||||
|
// Проверяем наличие идентификаторов в сессии
|
||||||
|
const sessionIdentities = [];
|
||||||
|
if (req.session.userId) sessionIdentities.push(`userId:${req.session.userId}`);
|
||||||
|
if (req.session.email) sessionIdentities.push(`email:${req.session.email}`);
|
||||||
|
if (req.session.address) sessionIdentities.push(`address:${req.session.address}`);
|
||||||
|
if (req.session.telegramId) sessionIdentities.push(`telegramId:${req.session.telegramId}`);
|
||||||
|
|
||||||
|
logger.info(`[session/check] Identities in session: ${sessionIdentities.join(', ')}`);
|
||||||
|
|
||||||
|
let identities = [];
|
||||||
|
let isAdmin = false;
|
||||||
|
|
||||||
|
if (authenticated && req.session.userId) {
|
||||||
|
// Если пользователь аутентифицирован, получаем его идентификаторы из БД
|
||||||
|
try {
|
||||||
|
const identitiesResult = await db.query(
|
||||||
|
`SELECT provider, provider_id FROM user_identities WHERE user_id = $1`,
|
||||||
|
[req.session.userId]
|
||||||
|
);
|
||||||
|
|
||||||
|
identities = identitiesResult.rows;
|
||||||
|
logger.info(`[session/check] Found ${identities.length} identities in database for user ${req.session.userId}`);
|
||||||
|
|
||||||
|
// Проверяем роль пользователя
|
||||||
|
const roleResult = await db.query(
|
||||||
|
'SELECT role FROM users WHERE id = $1',
|
||||||
|
[req.session.userId]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (roleResult.rows.length > 0) {
|
||||||
|
isAdmin = roleResult.rows[0].role === 'admin';
|
||||||
|
req.session.isAdmin = isAdmin;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`[session/check] Error fetching identities: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем, нужно ли создать новый гостевой ID
|
||||||
|
if (!authenticated && !req.session.guestId) {
|
||||||
|
req.session.guestId = crypto.randomBytes(16).toString('hex');
|
||||||
|
logger.info(`[session/check] Created new guest ID: ${req.session.guestId}`);
|
||||||
|
|
||||||
|
// Сохраняем сессию с новым гостевым ID
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
req.session.save(err => {
|
||||||
|
if (err) {
|
||||||
|
logger.error('[session/check] Error saving session with new guest ID:', err);
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
logger.info('[session/check] Session with new guest ID saved successfully');
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Возвращаем полную информацию об аутентифицированном пользователе
|
// Формируем ответ
|
||||||
const userData = {
|
const response = {
|
||||||
authenticated: true,
|
success: true,
|
||||||
userId: req.session.userId,
|
authenticated,
|
||||||
authType: req.session.authType,
|
userId: req.session.userId || null,
|
||||||
isAdmin: req.session.isAdmin || false
|
guestId: req.session.guestId || null,
|
||||||
|
authType,
|
||||||
|
identitiesCount: identities.length,
|
||||||
|
isAdmin: isAdmin || false
|
||||||
};
|
};
|
||||||
|
|
||||||
// Добавляем идентификаторы, если они есть в сессии
|
// Добавляем специфические поля в зависимости от типа аутентификации
|
||||||
if (req.session.address) userData.address = req.session.address;
|
if (authType === 'wallet') {
|
||||||
if (req.session.telegramId) userData.telegramId = req.session.telegramId;
|
response.address = req.session.address || null;
|
||||||
if (req.session.email) userData.email = req.session.email;
|
} else if (authType === 'email') {
|
||||||
|
response.email = req.session.email || null;
|
||||||
|
} else if (authType === 'telegram') {
|
||||||
|
response.telegramId = req.session.telegramId || null;
|
||||||
|
if (req.session.telegramUsername) {
|
||||||
|
response.telegramUsername = req.session.telegramUsername;
|
||||||
|
}
|
||||||
|
if (req.session.telegramFirstName) {
|
||||||
|
response.telegramFirstName = req.session.telegramFirstName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
console.log('Returning auth data:', userData);
|
logger.info(`[session/check] Session check complete: authenticated=${authenticated}, authType=${authType}`);
|
||||||
|
return res.json(response);
|
||||||
res.json(userData);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error checking auth status:', error);
|
logger.error('[session/check] Error:', error);
|
||||||
res.status(500).json({ error: 'Internal server error' });
|
return res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
error: 'Internal server error'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Выход из системы
|
// Выход из системы
|
||||||
router.post('/logout', (req, res) => {
|
router.post('/logout', async (req, res) => {
|
||||||
req.session.destroy((err) => {
|
try {
|
||||||
if (err) {
|
logger.info('[logout] Logout request received');
|
||||||
console.error('Error destroying session:', err);
|
|
||||||
return res.status(500).json({ error: 'Failed to logout' });
|
const sessionService = require('../services/session-service');
|
||||||
}
|
const result = await sessionService.logout(req.session);
|
||||||
res.json({ success: true });
|
|
||||||
|
if (result) {
|
||||||
|
logger.info('[logout] User successfully logged out');
|
||||||
|
return res.json({ success: true });
|
||||||
|
} else {
|
||||||
|
logger.warn('[logout] Error during logout process');
|
||||||
|
return res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
error: 'Error during logout process'
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[logout] Error:', error);
|
||||||
|
return res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
error: 'Internal server error'
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Маршрут для авторизации через Telegram
|
// Маршрут для авторизации через Telegram
|
||||||
@@ -1455,29 +1537,10 @@ async function linkGuestMessagesAfterAuth(session, userId) {
|
|||||||
return { success: true, message: 'No guest IDs to process' };
|
return { success: true, message: 'No guest IDs to process' };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Получаем список постоянных идентификаторов пользователя
|
// Инициализируем массив обработанных ID, если он не существует
|
||||||
const permanentIdentitiesResult = await db.query(
|
if (!session.processedGuestIds) {
|
||||||
`SELECT provider, provider_id FROM user_identities WHERE user_id = $1 AND provider IN ('wallet', 'email', 'telegram')`,
|
session.processedGuestIds = [];
|
||||||
[userId]
|
logger.info(`[linkGuestMessagesAfterAuth] Initialized processedGuestIds array for session`);
|
||||||
);
|
|
||||||
|
|
||||||
// Логирование найденных постоянных идентификаторов
|
|
||||||
logger.info(`[linkGuestMessagesAfterAuth] Found ${permanentIdentitiesResult.rows.length} permanent identities for user ${userId}`);
|
|
||||||
permanentIdentitiesResult.rows.forEach(identity => {
|
|
||||||
logger.info(`[linkGuestMessagesAfterAuth] - ${identity.provider}:${identity.provider_id}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Сохраняем все постоянные идентификаторы из сессии
|
|
||||||
if (session.email) {
|
|
||||||
await saveUserIdentity(userId, 'email', session.email, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (session.address) {
|
|
||||||
await saveUserIdentity(userId, 'wallet', session.address, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (session.telegramId) {
|
|
||||||
await saveUserIdentity(userId, 'telegram', session.telegramId, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let results = [];
|
let results = [];
|
||||||
@@ -1487,12 +1550,13 @@ async function linkGuestMessagesAfterAuth(session, userId) {
|
|||||||
logger.info(`[linkGuestMessagesAfterAuth] Processing current guest ID ${guestId} for user ${userId}`);
|
logger.info(`[linkGuestMessagesAfterAuth] Processing current guest ID ${guestId} for user ${userId}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Сохраняем гостевой ID как идентификатор пользователя
|
|
||||||
await saveUserIdentity(userId, 'guest', guestId, true);
|
|
||||||
|
|
||||||
// Связываем сообщения с пользователем через обертку с правильным порядком аргументов
|
// Связываем сообщения с пользователем через обертку с правильным порядком аргументов
|
||||||
const result = await processGuestMessagesWrapper(guestId, userId);
|
const result = await processGuestMessagesWrapper(guestId, userId);
|
||||||
results.push({ guestId, result });
|
results.push({ guestId, result });
|
||||||
|
|
||||||
|
// Добавляем в список обработанных
|
||||||
|
session.processedGuestIds.push(guestId);
|
||||||
|
|
||||||
logger.info(`[linkGuestMessagesAfterAuth] Successfully processed guest ID ${guestId}`);
|
logger.info(`[linkGuestMessagesAfterAuth] Successfully processed guest ID ${guestId}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`[linkGuestMessagesAfterAuth] Error processing guest ID ${guestId}:`, error);
|
logger.error(`[linkGuestMessagesAfterAuth] Error processing guest ID ${guestId}:`, error);
|
||||||
@@ -1505,12 +1569,13 @@ async function linkGuestMessagesAfterAuth(session, userId) {
|
|||||||
logger.info(`[linkGuestMessagesAfterAuth] Processing previous guest ID ${previousGuestId} for user ${userId}`);
|
logger.info(`[linkGuestMessagesAfterAuth] Processing previous guest ID ${previousGuestId} for user ${userId}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Сохраняем предыдущий гостевой ID как идентификатор пользователя
|
|
||||||
await saveUserIdentity(userId, 'guest', previousGuestId, true);
|
|
||||||
|
|
||||||
// Связываем сообщения с пользователем через обертку с правильным порядком аргументов
|
// Связываем сообщения с пользователем через обертку с правильным порядком аргументов
|
||||||
const result = await processGuestMessagesWrapper(previousGuestId, userId);
|
const result = await processGuestMessagesWrapper(previousGuestId, userId);
|
||||||
results.push({ guestId: previousGuestId, result });
|
results.push({ guestId: previousGuestId, result });
|
||||||
|
|
||||||
|
// Добавляем в список обработанных
|
||||||
|
session.processedGuestIds.push(previousGuestId);
|
||||||
|
|
||||||
logger.info(`[linkGuestMessagesAfterAuth] Successfully processed previous guest ID ${previousGuestId}`);
|
logger.info(`[linkGuestMessagesAfterAuth] Successfully processed previous guest ID ${previousGuestId}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`[linkGuestMessagesAfterAuth] Error processing previous guest ID ${previousGuestId}:`, error);
|
logger.error(`[linkGuestMessagesAfterAuth] Error processing previous guest ID ${previousGuestId}:`, error);
|
||||||
@@ -1688,6 +1753,11 @@ router.post('/link-guest-messages', requireAuth, async (req, res) => {
|
|||||||
|
|
||||||
logger.info(`[link-guest-messages] Request for user ${userId}`);
|
logger.info(`[link-guest-messages] Request for user ${userId}`);
|
||||||
|
|
||||||
|
// Проверка кэша обработанных ID в сессии
|
||||||
|
if (!req.session.processedGuestIds) {
|
||||||
|
req.session.processedGuestIds = [];
|
||||||
|
}
|
||||||
|
|
||||||
// Получаем все идентификаторы пользователя
|
// Получаем все идентификаторы пользователя
|
||||||
const userIdentitiesResult = await db.query(
|
const userIdentitiesResult = await db.query(
|
||||||
`SELECT provider, provider_id FROM user_identities WHERE user_id = $1`,
|
`SELECT provider, provider_id FROM user_identities WHERE user_id = $1`,
|
||||||
@@ -1696,66 +1766,44 @@ router.post('/link-guest-messages', requireAuth, async (req, res) => {
|
|||||||
|
|
||||||
const userIdentities = userIdentitiesResult.rows;
|
const userIdentities = userIdentitiesResult.rows;
|
||||||
logger.info(`[link-guest-messages] Found ${userIdentities.length} identities for user ${userId}`);
|
logger.info(`[link-guest-messages] Found ${userIdentities.length} identities for user ${userId}`);
|
||||||
userIdentities.forEach(identity => {
|
|
||||||
logger.info(`[link-guest-messages] - ${identity.provider}:${identity.provider_id}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Собираем все guestId, связанные с идентификаторами пользователя
|
// Получаем только гостевые идентификаторы и фильтруем по неообработанным
|
||||||
let guestIds = [];
|
const guestIdentities = userIdentities
|
||||||
|
.filter(identity => identity.provider === 'guest')
|
||||||
|
.filter(identity => !req.session.processedGuestIds.includes(identity.provider_id));
|
||||||
|
|
||||||
// Добавляем текущий guestId из сессии
|
// Добавляем текущий guestId из сессии если он еще не обработан
|
||||||
if (req.session.guestId) {
|
if (req.session.guestId && !req.session.processedGuestIds.includes(req.session.guestId)) {
|
||||||
guestIds.push(req.session.guestId);
|
guestIdentities.push({ provider: 'guest', provider_id: req.session.guestId });
|
||||||
logger.info(`[link-guest-messages] Added session guestId: ${req.session.guestId}`);
|
logger.info(`[link-guest-messages] Added session guestId: ${req.session.guestId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Добавляем guestId из тела запроса, если есть
|
// Добавляем guestId из тела запроса, если есть и еще не обработан
|
||||||
if (req.body.guestId) {
|
if (req.body.guestId && !req.session.processedGuestIds.includes(req.body.guestId)) {
|
||||||
guestIds.push(req.body.guestId);
|
guestIdentities.push({ provider: 'guest', provider_id: req.body.guestId });
|
||||||
logger.info(`[link-guest-messages] Added request body guestId: ${req.body.guestId}`);
|
logger.info(`[link-guest-messages] Added request body guestId: ${req.body.guestId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Добавляем массив guestIds из тела запроса, если есть
|
|
||||||
if (req.body.guestIds && Array.isArray(req.body.guestIds)) {
|
|
||||||
logger.info(`[link-guest-messages] Adding ${req.body.guestIds.length} guestIds from request body`);
|
|
||||||
guestIds = [...guestIds, ...req.body.guestIds];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Добавляем guestId из идентификаторов пользователя
|
|
||||||
const guestIdentities = userIdentities.filter(identity => identity.provider === 'guest');
|
|
||||||
if (guestIdentities.length > 0) {
|
|
||||||
logger.info(`[link-guest-messages] Adding ${guestIdentities.length} guest identities from user_identities`);
|
|
||||||
guestIds = [...guestIds, ...guestIdentities.map(identity => identity.provider_id)];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Убираем дубликаты
|
// Убираем дубликаты
|
||||||
guestIds = [...new Set(guestIds)];
|
const uniqueGuestIds = [...new Set(guestIdentities.map(i => i.provider_id))];
|
||||||
|
|
||||||
logger.info(`[link-guest-messages] Final list of unique guestIds to process: ${guestIds.length}`);
|
// Если все ID уже обработаны, сразу возвращаем успех
|
||||||
guestIds.forEach(id => logger.info(`[link-guest-messages] - ${id}`));
|
if (uniqueGuestIds.length === 0) {
|
||||||
|
logger.info('[link-guest-messages] No new guest IDs to process');
|
||||||
if (guestIds.length === 0) {
|
return res.json({
|
||||||
logger.info('[link-guest-messages] No guest IDs found for user');
|
success: true,
|
||||||
return res.json({ success: true, message: 'No guest messages to link' });
|
message: 'All guest IDs already processed',
|
||||||
|
processedIds: req.session.processedGuestIds
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.info(`[link-guest-messages] Found ${uniqueGuestIds.length} new guestIds to process`);
|
||||||
|
uniqueGuestIds.forEach(id => logger.info(`[link-guest-messages] - ${id}`));
|
||||||
|
|
||||||
const results = [];
|
const results = [];
|
||||||
|
|
||||||
// Обрабатываем каждый guestId
|
// Обрабатываем каждый новый guestId
|
||||||
for (const guestId of guestIds) {
|
for (const guestId of uniqueGuestIds) {
|
||||||
// Пропускаем пустые или невалидные ID
|
|
||||||
if (!guestId || typeof guestId !== 'string') {
|
|
||||||
logger.warn(`[link-guest-messages] Skipping invalid guest ID: ${guestId}`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Проверяем, не обрабатывали ли мы уже этот ID
|
|
||||||
if (req.session.processedGuestIds && req.session.processedGuestIds.includes(guestId)) {
|
|
||||||
logger.info(`[link-guest-messages] Guest ID ${guestId} already processed, skipping`);
|
|
||||||
results.push({ guestId, result: { success: true, message: 'Already processed' } });
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Проверяем наличие гостевых сообщений
|
// Проверяем наличие гостевых сообщений
|
||||||
try {
|
try {
|
||||||
const guestMessagesCheck = await db.query(
|
const guestMessagesCheck = await db.query(
|
||||||
@@ -1769,6 +1817,10 @@ router.post('/link-guest-messages', requireAuth, async (req, res) => {
|
|||||||
// Используем обертку с правильным порядком аргументов
|
// Используем обертку с правильным порядком аргументов
|
||||||
const result = await processGuestMessagesWrapper(guestId, userId);
|
const result = await processGuestMessagesWrapper(guestId, userId);
|
||||||
results.push({ guestId, result });
|
results.push({ guestId, result });
|
||||||
|
|
||||||
|
// Добавляем в список обработанных
|
||||||
|
req.session.processedGuestIds.push(guestId);
|
||||||
|
|
||||||
logger.info(`[link-guest-messages] Successfully processed guest ID ${guestId}`);
|
logger.info(`[link-guest-messages] Successfully processed guest ID ${guestId}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`[link-guest-messages] Error processing guest ID ${guestId}:`, error);
|
logger.error(`[link-guest-messages] Error processing guest ID ${guestId}:`, error);
|
||||||
@@ -1776,6 +1828,8 @@ router.post('/link-guest-messages', requireAuth, async (req, res) => {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.info(`[link-guest-messages] No guest messages found for guest ID ${guestId}`);
|
logger.info(`[link-guest-messages] No guest messages found for guest ID ${guestId}`);
|
||||||
|
// Всё равно добавляем в обработанные, чтобы не проверять снова
|
||||||
|
req.session.processedGuestIds.push(guestId);
|
||||||
results.push({ guestId, result: { success: true, message: 'No messages found' } });
|
results.push({ guestId, result: { success: true, message: 'No messages found' } });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -1784,19 +1838,6 @@ router.post('/link-guest-messages', requireAuth, async (req, res) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сохраняем все обработанные guestId, чтобы не обрабатывать их снова
|
|
||||||
if (!req.session.processedGuestIds) {
|
|
||||||
req.session.processedGuestIds = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Добавляем только успешно обработанные ID
|
|
||||||
const successfulGuestIds = results
|
|
||||||
.filter(r => r.result && r.result.success)
|
|
||||||
.map(r => r.guestId);
|
|
||||||
|
|
||||||
req.session.processedGuestIds = [...new Set([...req.session.processedGuestIds, ...successfulGuestIds])];
|
|
||||||
logger.info(`[link-guest-messages] Updated processed guest IDs: ${req.session.processedGuestIds.join(', ')}`);
|
|
||||||
|
|
||||||
// Очищаем текущий guestId из сессии
|
// Очищаем текущий guestId из сессии
|
||||||
req.session.guestId = null;
|
req.session.guestId = null;
|
||||||
await req.session.save();
|
await req.session.save();
|
||||||
@@ -1805,7 +1846,8 @@ router.post('/link-guest-messages', requireAuth, async (req, res) => {
|
|||||||
return res.json({
|
return res.json({
|
||||||
success: true,
|
success: true,
|
||||||
message: `Processed ${results.length} guest IDs`,
|
message: `Processed ${results.length} guest IDs`,
|
||||||
results
|
results,
|
||||||
|
processedIds: req.session.processedGuestIds
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[link-guest-messages] Error:', error);
|
logger.error('[link-guest-messages] Error:', error);
|
||||||
|
|||||||
@@ -142,19 +142,25 @@ async function processGuestMessages(userId, guestId) {
|
|||||||
// Обработчик для гостевых сообщений
|
// Обработчик для гостевых сообщений
|
||||||
router.post('/guest-message', async (req, res) => {
|
router.post('/guest-message', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { message, language } = req.body;
|
const { content, language, guestId: requestGuestId } = req.body;
|
||||||
const guestId = req.session.guestId || crypto.randomBytes(16).toString('hex');
|
|
||||||
|
if (!content) {
|
||||||
|
return res.status(400).json({ success: false, error: 'Content is required' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Используем гостевой ID из запроса или из сессии, или генерируем новый
|
||||||
|
const guestId = requestGuestId || req.session.guestId || crypto.randomBytes(16).toString('hex');
|
||||||
|
|
||||||
// Сохраняем ID гостя в сессии
|
// Сохраняем ID гостя в сессии
|
||||||
req.session.guestId = guestId;
|
req.session.guestId = guestId;
|
||||||
await req.session.save();
|
await req.session.save();
|
||||||
|
|
||||||
console.log('Saving guest message:', { guestId, message });
|
console.log('Saving guest message:', { guestId, content });
|
||||||
|
|
||||||
// Сохраняем сообщение пользователя
|
// Сохраняем сообщение пользователя
|
||||||
const result = await db.query(
|
const result = await db.query(
|
||||||
'INSERT INTO guest_messages (guest_id, content, language, is_ai) VALUES ($1, $2, $3, false) RETURNING id',
|
'INSERT INTO guest_messages (guest_id, content, language, is_ai) VALUES ($1, $2, $3, false) RETURNING id',
|
||||||
[guestId, message, language]
|
[guestId, content, language || 'auto']
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('Guest message saved:', result.rows[0]);
|
console.log('Guest message saved:', result.rows[0]);
|
||||||
|
|||||||
200
backend/services/identity-service.js
Normal file
200
backend/services/identity-service.js
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
const db = require('../db');
|
||||||
|
const logger = require('../utils/logger');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сервис для работы с идентификаторами пользователей
|
||||||
|
*/
|
||||||
|
class IdentityService {
|
||||||
|
/**
|
||||||
|
* Сохраняет идентификатор пользователя в базу данных
|
||||||
|
* @param {number} userId - ID пользователя
|
||||||
|
* @param {string} provider - Тип идентификатора (wallet, email, telegram, guest)
|
||||||
|
* @param {string} providerId - Значение идентификатора
|
||||||
|
* @param {boolean} verified - Флаг верификации идентификатора
|
||||||
|
* @returns {Promise<object>} - Результат операции
|
||||||
|
*/
|
||||||
|
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'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`[IdentityService] Saving identity for user ${userId}: ${provider}:${providerId}`);
|
||||||
|
|
||||||
|
// Проверяем, существует ли уже такой идентификатор
|
||||||
|
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(`[IdentityService] Identity ${provider}:${providerId} already exists for user ${userId}`);
|
||||||
|
} else {
|
||||||
|
// Если идентификатор принадлежит другому пользователю, логируем это
|
||||||
|
logger.warn(`[IdentityService] Identity ${provider}:${providerId} already belongs to user ${existingUserId}, not user ${userId}`);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: `Identity already belongs to another user (${existingUserId})`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Создаем новую запись
|
||||||
|
await db.query(
|
||||||
|
`INSERT INTO user_identities (user_id, provider, provider_id)
|
||||||
|
VALUES ($1, $2, $3)`,
|
||||||
|
[userId, provider, providerId]
|
||||||
|
);
|
||||||
|
logger.info(`[IdentityService] Created new identity ${provider}:${providerId} for user ${userId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true };
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`[IdentityService] Error saving identity ${provider}:${providerId} for user ${userId}:`, error);
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получает все идентификаторы пользователя
|
||||||
|
* @param {number} userId - ID пользователя
|
||||||
|
* @returns {Promise<Array>} - Массив идентификаторов
|
||||||
|
*/
|
||||||
|
async getUserIdentities(userId) {
|
||||||
|
try {
|
||||||
|
if (!userId) {
|
||||||
|
logger.warn('[IdentityService] Missing userId parameter');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await db.query(
|
||||||
|
`SELECT provider, provider_id FROM user_identities WHERE user_id = $1`,
|
||||||
|
[userId]
|
||||||
|
);
|
||||||
|
|
||||||
|
logger.info(`[IdentityService] Found ${result.rows.length} identities for user ${userId}`);
|
||||||
|
return result.rows;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`[IdentityService] Error getting identities for user ${userId}:`, error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получает все идентификаторы пользователя определенного типа
|
||||||
|
* @param {number} userId - ID пользователя
|
||||||
|
* @param {string} provider - Тип идентификатора
|
||||||
|
* @returns {Promise<Array>} - Массив идентификаторов
|
||||||
|
*/
|
||||||
|
async getUserIdentitiesByProvider(userId, provider) {
|
||||||
|
try {
|
||||||
|
if (!userId || !provider) {
|
||||||
|
logger.warn(`[IdentityService] Missing parameters: userId=${userId}, provider=${provider}`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await db.query(
|
||||||
|
`SELECT provider_id FROM user_identities WHERE user_id = $1 AND provider = $2`,
|
||||||
|
[userId, provider]
|
||||||
|
);
|
||||||
|
|
||||||
|
logger.info(`[IdentityService] Found ${result.rows.length} ${provider} identities for user ${userId}`);
|
||||||
|
return result.rows.map(row => row.provider_id);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`[IdentityService] Error getting ${provider} identities for user ${userId}:`, error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Находит пользователя по идентификатору
|
||||||
|
* @param {string} provider - Тип идентификатора
|
||||||
|
* @param {string} providerId - Значение идентификатора
|
||||||
|
* @returns {Promise<object|null>} - Информация о пользователе или null
|
||||||
|
*/
|
||||||
|
async findUserByIdentity(provider, providerId) {
|
||||||
|
try {
|
||||||
|
if (!provider || !providerId) {
|
||||||
|
logger.warn(`[IdentityService] Missing parameters: provider=${provider}, providerId=${providerId}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await db.query(
|
||||||
|
`SELECT u.id, u.role FROM users u
|
||||||
|
JOIN user_identities ui ON u.id = ui.user_id
|
||||||
|
WHERE ui.provider = $1 AND ui.provider_id = $2`,
|
||||||
|
[provider, providerId]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.rows.length === 0) {
|
||||||
|
logger.info(`[IdentityService] No user found with identity ${provider}:${providerId}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`[IdentityService] Found user ${result.rows[0].id} with identity ${provider}:${providerId}`);
|
||||||
|
return result.rows[0];
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`[IdentityService] Error finding user by identity ${provider}:${providerId}:`, error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сохраняет идентификаторы из сессии для пользователя
|
||||||
|
* @param {object} session - Объект сессии
|
||||||
|
* @param {number} userId - ID пользователя
|
||||||
|
* @returns {Promise<object>} - Результат операции
|
||||||
|
*/
|
||||||
|
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.toLowerCase(), true);
|
||||||
|
results.push({ type: 'email', result: emailResult });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session.address) {
|
||||||
|
const walletResult = await this.saveIdentity(userId, 'wallet', session.address.toLowerCase(), 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 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сохраняем гостевые идентификаторы
|
||||||
|
if (session.guestId) {
|
||||||
|
const guestResult = await this.saveIdentity(userId, 'guest', session.guestId, true);
|
||||||
|
results.push({ type: 'guest', result: guestResult });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session.previousGuestId && session.previousGuestId !== session.guestId) {
|
||||||
|
const prevGuestResult = await this.saveIdentity(userId, 'guest', session.previousGuestId, true);
|
||||||
|
results.push({ type: 'previousGuest', result: prevGuestResult });
|
||||||
|
}
|
||||||
|
|
||||||
|
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 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new IdentityService();
|
||||||
169
backend/services/session-service.js
Normal file
169
backend/services/session-service.js
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
const logger = require('../utils/logger');
|
||||||
|
const db = require('../db');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сервис для работы с сессиями пользователей
|
||||||
|
*/
|
||||||
|
class SessionService {
|
||||||
|
/**
|
||||||
|
* Сохраняет сессию с обработкой ошибок
|
||||||
|
* @param {object} session - Объект сессии
|
||||||
|
* @param {string} context - Контекст для логирования
|
||||||
|
* @returns {Promise<boolean>} - Результат операции
|
||||||
|
*/
|
||||||
|
async saveSession(session, context = '') {
|
||||||
|
if (!session) {
|
||||||
|
logger.warn(`[SessionService${context ? '/' + context : ''}] Cannot save null session`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await new Promise((resolve, reject) => {
|
||||||
|
session.save(err => {
|
||||||
|
if (err) {
|
||||||
|
logger.error(`[SessionService${context ? '/' + context : ''}] Error saving session:`, err);
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
logger.info(`[SessionService${context ? '/' + context : ''}] Session saved successfully`);
|
||||||
|
resolve(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`[SessionService${context ? '/' + context : ''}] Error in saveSession:`, error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Восстанавливает сессию из базы данных по ID
|
||||||
|
* @param {string} sessionId - ID сессии
|
||||||
|
* @returns {Promise<object|null>} - Данные сессии или null
|
||||||
|
*/
|
||||||
|
async getSessionData(sessionId) {
|
||||||
|
try {
|
||||||
|
if (!sessionId) {
|
||||||
|
logger.warn('[SessionService] Cannot restore session without sessionId');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`[SessionService] Attempting to retrieve session ${sessionId}`);
|
||||||
|
|
||||||
|
const result = await db.query(
|
||||||
|
'SELECT sess FROM session WHERE sid = $1',
|
||||||
|
[sessionId]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.rows.length === 0) {
|
||||||
|
logger.info(`[SessionService] No session found with ID ${sessionId}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sessionData = result.rows[0].sess;
|
||||||
|
logger.info(`[SessionService] Retrieved session data for ${sessionId}`);
|
||||||
|
|
||||||
|
return sessionData;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`[SessionService] Error retrieving session ${sessionId}:`, error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обновляет данные аутентификации в сессии
|
||||||
|
* @param {object} session - Объект сессии
|
||||||
|
* @param {object} authData - Данные аутентификации
|
||||||
|
* @returns {Promise<boolean>} - Результат операции
|
||||||
|
*/
|
||||||
|
async updateAuthData(session, authData) {
|
||||||
|
try {
|
||||||
|
if (!session || !authData) {
|
||||||
|
logger.warn('[SessionService] Missing parameters for updateAuthData');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { userId, authType, isAdmin, ...otherData } = authData;
|
||||||
|
|
||||||
|
if (!userId || !authType) {
|
||||||
|
logger.warn('[SessionService] Missing userId or authType in authData');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем основные поля аутентификации
|
||||||
|
session.userId = userId;
|
||||||
|
session.authType = authType;
|
||||||
|
session.authenticated = true;
|
||||||
|
|
||||||
|
if (isAdmin !== undefined) {
|
||||||
|
session.isAdmin = isAdmin;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем дополнительные данные в зависимости от типа аутентификации
|
||||||
|
if (authType === 'wallet' && otherData.address) {
|
||||||
|
session.address = otherData.address.toLowerCase();
|
||||||
|
} else if (authType === 'email' && otherData.email) {
|
||||||
|
session.email = otherData.email.toLowerCase();
|
||||||
|
} else if (authType === 'telegram') {
|
||||||
|
if (otherData.telegramId) session.telegramId = otherData.telegramId;
|
||||||
|
if (otherData.telegramUsername) session.telegramUsername = otherData.telegramUsername;
|
||||||
|
if (otherData.telegramFirstName) session.telegramFirstName = otherData.telegramFirstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сохраняем гостевые ID, если они предоставлены и не были ранее в сессии
|
||||||
|
if (otherData.guestId && !session.guestId) {
|
||||||
|
session.guestId = otherData.guestId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (otherData.previousGuestId && !session.previousGuestId) {
|
||||||
|
session.previousGuestId = otherData.previousGuestId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сохраняем обновленную сессию
|
||||||
|
return await this.saveSession(session, 'updateAuthData');
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[SessionService] Error updating auth data:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Очищает данные аутентификации в сессии
|
||||||
|
* @param {object} session - Объект сессии
|
||||||
|
* @returns {Promise<boolean>} - Результат операции
|
||||||
|
*/
|
||||||
|
async logout(session) {
|
||||||
|
try {
|
||||||
|
if (!session) {
|
||||||
|
logger.warn('[SessionService] Cannot logout null session');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сохраняем гостевые ID перед очисткой
|
||||||
|
const guestId = session.guestId;
|
||||||
|
|
||||||
|
// Удаляем данные аутентификации
|
||||||
|
delete session.userId;
|
||||||
|
delete session.authenticated;
|
||||||
|
delete session.authType;
|
||||||
|
delete session.isAdmin;
|
||||||
|
delete session.address;
|
||||||
|
delete session.email;
|
||||||
|
delete session.telegramId;
|
||||||
|
delete session.telegramUsername;
|
||||||
|
delete session.telegramFirstName;
|
||||||
|
|
||||||
|
// Восстанавливаем гостевой ID для продолжения работы
|
||||||
|
if (guestId) {
|
||||||
|
session.guestId = guestId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сохраняем обновленную сессию
|
||||||
|
return await this.saveSession(session, 'logout');
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[SessionService] Error during logout:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new SessionService();
|
||||||
@@ -216,6 +216,7 @@ input, textarea {
|
|||||||
padding: 10px 15px;
|
padding: 10px 15px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
max-width: 80%;
|
max-width: 80%;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-message {
|
.user-message {
|
||||||
@@ -229,18 +230,58 @@ input, textarea {
|
|||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.system-message {
|
||||||
|
background-color: #FFF3E0;
|
||||||
|
align-self: center;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
font-style: italic;
|
||||||
|
color: #FF5722;
|
||||||
|
text-align: center;
|
||||||
|
max-width: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
.message-content {
|
.message-content {
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message-meta {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.message-time {
|
.message-time {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #777;
|
color: #777;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message-status {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #777;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sending-indicator {
|
||||||
|
color: #2196F3;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-indicator {
|
||||||
|
color: #F44336;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-local {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.has-error {
|
||||||
|
border: 1px solid #F44336;
|
||||||
|
}
|
||||||
|
|
||||||
.chat-input {
|
.chat-input {
|
||||||
display: flex;
|
display: flex;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
@@ -1015,3 +1056,121 @@ input, textarea {
|
|||||||
color: #666;
|
color: #666;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Стили для отображения подключенного кошелька */
|
||||||
|
.wallet-connected .wallet-button {
|
||||||
|
background-color: #4CAF50 !important;
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wallet-connected #auth-display {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background-color: rgba(76, 175, 80, 0.1);
|
||||||
|
border: 1px solid #4CAF50;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-right: 10px;
|
||||||
|
color: #4CAF50;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Индикатор подключения */
|
||||||
|
.connection-indicator {
|
||||||
|
display: inline-block;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 8px;
|
||||||
|
background-color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wallet-connected .connection-indicator {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Стили для кнопок авторизации */
|
||||||
|
#auth-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#logout-button {
|
||||||
|
display: none;
|
||||||
|
background-color: #f44336;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
#logout-button:hover {
|
||||||
|
background-color: #d32f2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Анимация для индикации подключения */
|
||||||
|
@keyframes pulse {
|
||||||
|
0% {
|
||||||
|
box-shadow: 0 0 0 0 rgba(76, 175, 80, 0.7);
|
||||||
|
}
|
||||||
|
70% {
|
||||||
|
box-shadow: 0 0 0 10px rgba(76, 175, 80, 0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
box-shadow: 0 0 0 0 rgba(76, 175, 80, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wallet-connected .connection-indicator {
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Стили для отладочной информации */
|
||||||
|
.debug-info {
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-info h4 {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-item {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-item code {
|
||||||
|
background-color: #e0e0e0;
|
||||||
|
padding: 2px 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: monospace;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small-button {
|
||||||
|
padding: 5px 10px;
|
||||||
|
background-color: #5e5e5e;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small-button:hover {
|
||||||
|
background-color: #444;
|
||||||
|
}
|
||||||
|
|||||||
@@ -74,9 +74,23 @@ export function useAuth() {
|
|||||||
if (isAuthenticated.value) {
|
if (isAuthenticated.value) {
|
||||||
console.log('Linking messages after authentication');
|
console.log('Linking messages after authentication');
|
||||||
|
|
||||||
|
// Проверка, есть ли гостевой ID для обработки
|
||||||
|
const localGuestId = localStorage.getItem('guestId');
|
||||||
|
|
||||||
|
// Если гостевого ID нет или он уже был обработан, пропускаем запрос
|
||||||
|
if (!localGuestId || processedGuestIds.value.includes(localGuestId)) {
|
||||||
|
console.log('No new guest IDs to process or already processed');
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'No new guest IDs to process',
|
||||||
|
processedIds: processedGuestIds.value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Создаем объект с идентификаторами для передачи на сервер
|
// Создаем объект с идентификаторами для передачи на сервер
|
||||||
const identifiersData = {
|
const identifiersData = {
|
||||||
userId: userId.value
|
userId: userId.value,
|
||||||
|
guestId: localGuestId
|
||||||
};
|
};
|
||||||
|
|
||||||
// Добавляем все доступные идентификаторы
|
// Добавляем все доступные идентификаторы
|
||||||
@@ -84,17 +98,6 @@ export function useAuth() {
|
|||||||
if (email.value) identifiersData.email = email.value;
|
if (email.value) identifiersData.email = email.value;
|
||||||
if (telegramId.value) identifiersData.telegramId = telegramId.value;
|
if (telegramId.value) identifiersData.telegramId = telegramId.value;
|
||||||
|
|
||||||
// Сохраняем предыдущий guestId из localStorage, если есть
|
|
||||||
const localGuestId = localStorage.getItem('guestId');
|
|
||||||
if (localGuestId && !processedGuestIds.value.includes(localGuestId)) {
|
|
||||||
console.log('Found local guestId:', localGuestId);
|
|
||||||
// Добавляем guestId в идентификаторы
|
|
||||||
identifiersData.guestId = localGuestId;
|
|
||||||
// Добавляем guestId в список обработанных
|
|
||||||
processedGuestIds.value.push(localGuestId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Логируем попытку связывания сообщений
|
|
||||||
console.log('Sending link-guest-messages request with data:', identifiersData);
|
console.log('Sending link-guest-messages request with data:', identifiersData);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -104,15 +107,20 @@ export function useAuth() {
|
|||||||
if (response.data.success) {
|
if (response.data.success) {
|
||||||
console.log('Messages linked successfully:', response.data);
|
console.log('Messages linked successfully:', response.data);
|
||||||
|
|
||||||
// Если в ответе есть обработанные guestIds, добавляем их в список
|
// Обновляем список обработанных guestIds из ответа сервера
|
||||||
if (response.data.results && Array.isArray(response.data.results)) {
|
if (response.data.processedIds && Array.isArray(response.data.processedIds)) {
|
||||||
|
processedGuestIds.value = [...response.data.processedIds];
|
||||||
|
console.log('Updated processed guest IDs from server:', processedGuestIds.value);
|
||||||
|
}
|
||||||
|
// В качестве запасного варианта также обрабатываем старый формат ответа
|
||||||
|
else if (response.data.results && Array.isArray(response.data.results)) {
|
||||||
const newProcessedIds = response.data.results
|
const newProcessedIds = response.data.results
|
||||||
.filter(result => result.guestId)
|
.filter(result => result.guestId)
|
||||||
.map(result => result.guestId);
|
.map(result => result.guestId);
|
||||||
|
|
||||||
if (newProcessedIds.length > 0) {
|
if (newProcessedIds.length > 0) {
|
||||||
processedGuestIds.value = [...new Set([...processedGuestIds.value, ...newProcessedIds])];
|
processedGuestIds.value = [...new Set([...processedGuestIds.value, ...newProcessedIds])];
|
||||||
console.log('Updated processed guest IDs:', processedGuestIds.value);
|
console.log('Updated processed guest IDs from results:', processedGuestIds.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,6 +156,7 @@ export function useAuth() {
|
|||||||
|
|
||||||
const wasAuthenticated = isAuthenticated.value;
|
const wasAuthenticated = isAuthenticated.value;
|
||||||
const previousUserId = userId.value;
|
const previousUserId = userId.value;
|
||||||
|
const previousAuthType = authType.value;
|
||||||
|
|
||||||
// Обновляем данные авторизации через updateAuth вместо прямого изменения
|
// Обновляем данные авторизации через updateAuth вместо прямого изменения
|
||||||
updateAuth({
|
updateAuth({
|
||||||
@@ -171,12 +180,28 @@ export function useAuth() {
|
|||||||
// Немедленно связываем сообщения
|
// Немедленно связываем сообщения
|
||||||
const linkResult = await linkMessages();
|
const linkResult = await linkMessages();
|
||||||
console.log('Link messages result on auth change:', linkResult);
|
console.log('Link messages result on auth change:', linkResult);
|
||||||
|
|
||||||
|
// Если пользователь только что аутентифицировался через Telegram,
|
||||||
|
// обновляем историю чата без перезагрузки страницы
|
||||||
|
if (response.data.authType === 'telegram' && previousAuthType !== 'telegram') {
|
||||||
|
console.log('Telegram auth detected, loading message history');
|
||||||
|
// Отправляем событие для загрузки истории чата
|
||||||
|
window.dispatchEvent(new CustomEvent('load-chat-history'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Обновляем отображение подключенного состояния в UI
|
||||||
|
updateConnectionDisplay(true, response.data.authType, response.data);
|
||||||
|
} else {
|
||||||
|
// Обновляем отображение отключенного состояния
|
||||||
|
updateConnectionDisplay(false);
|
||||||
|
}
|
||||||
|
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error checking auth:', error);
|
console.error('Error checking auth:', error);
|
||||||
|
// В случае ошибки сбрасываем состояние аутентификации
|
||||||
|
updateConnectionDisplay(false);
|
||||||
return { authenticated: false };
|
return { authenticated: false };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -189,6 +214,8 @@ export function useAuth() {
|
|||||||
console.log('Created new guestId for future session:', newGuestId);
|
console.log('Created new guestId for future session:', newGuestId);
|
||||||
|
|
||||||
await axios.post('/api/auth/logout');
|
await axios.post('/api/auth/logout');
|
||||||
|
|
||||||
|
// Обновляем состояние в памяти
|
||||||
updateAuth({
|
updateAuth({
|
||||||
authenticated: false,
|
authenticated: false,
|
||||||
authType: null,
|
authType: null,
|
||||||
@@ -199,6 +226,9 @@ export function useAuth() {
|
|||||||
isAdmin: false
|
isAdmin: false
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Обновляем отображение отключенного состояния
|
||||||
|
updateConnectionDisplay(false);
|
||||||
|
|
||||||
// Очищаем списки идентификаторов
|
// Очищаем списки идентификаторов
|
||||||
identities.value = [];
|
identities.value = [];
|
||||||
|
|
||||||
@@ -208,6 +238,11 @@ export function useAuth() {
|
|||||||
localStorage.removeItem('address');
|
localStorage.removeItem('address');
|
||||||
localStorage.removeItem('isAdmin');
|
localStorage.removeItem('isAdmin');
|
||||||
|
|
||||||
|
// Удаляем класс подключенного кошелька
|
||||||
|
document.body.classList.remove('wallet-connected');
|
||||||
|
|
||||||
|
console.log('User disconnected successfully');
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error disconnecting:', error);
|
console.error('Error disconnecting:', error);
|
||||||
@@ -222,6 +257,58 @@ export function useAuth() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Функция для обновления отображения подключения в UI
|
||||||
|
const updateConnectionDisplay = (isConnected, authType, authData = {}) => {
|
||||||
|
try {
|
||||||
|
console.log('Updating connection display:', { isConnected, authType, authData });
|
||||||
|
|
||||||
|
if (isConnected) {
|
||||||
|
document.body.classList.add('wallet-connected');
|
||||||
|
|
||||||
|
const authDisplayEl = document.getElementById('auth-display');
|
||||||
|
if (authDisplayEl) {
|
||||||
|
let displayText = 'Подключено';
|
||||||
|
|
||||||
|
if (authType === 'wallet' && authData.address) {
|
||||||
|
const shortAddress = `${authData.address.substring(0, 6)}...${authData.address.substring(authData.address.length - 4)}`;
|
||||||
|
displayText = `Кошелек: <strong>${shortAddress}</strong>`;
|
||||||
|
} else if (authType === 'email' && authData.email) {
|
||||||
|
displayText = `Email: <strong>${authData.email}</strong>`;
|
||||||
|
} else if (authType === 'telegram' && authData.telegramId) {
|
||||||
|
displayText = `Telegram: <strong>${authData.telegramUsername || authData.telegramId}</strong>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
authDisplayEl.innerHTML = displayText;
|
||||||
|
authDisplayEl.style.display = 'inline-block';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Скрываем кнопки авторизации и показываем кнопку выхода
|
||||||
|
const authButtonsEl = document.getElementById('auth-buttons');
|
||||||
|
const logoutButtonEl = document.getElementById('logout-button');
|
||||||
|
|
||||||
|
if (authButtonsEl) authButtonsEl.style.display = 'none';
|
||||||
|
if (logoutButtonEl) logoutButtonEl.style.display = 'inline-block';
|
||||||
|
} else {
|
||||||
|
document.body.classList.remove('wallet-connected');
|
||||||
|
|
||||||
|
// Скрываем отображение аутентификации
|
||||||
|
const authDisplayEl = document.getElementById('auth-display');
|
||||||
|
if (authDisplayEl) {
|
||||||
|
authDisplayEl.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Показываем кнопки авторизации и скрываем кнопку выхода
|
||||||
|
const authButtonsEl = document.getElementById('auth-buttons');
|
||||||
|
const logoutButtonEl = document.getElementById('logout-button');
|
||||||
|
|
||||||
|
if (authButtonsEl) authButtonsEl.style.display = 'flex';
|
||||||
|
if (logoutButtonEl) logoutButtonEl.style.display = 'none';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating connection display:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await checkAuth();
|
await checkAuth();
|
||||||
});
|
});
|
||||||
@@ -241,6 +328,7 @@ export function useAuth() {
|
|||||||
disconnect,
|
disconnect,
|
||||||
linkMessages,
|
linkMessages,
|
||||||
updateIdentities,
|
updateIdentities,
|
||||||
updateProcessedGuestIds
|
updateProcessedGuestIds,
|
||||||
|
updateConnectionDisplay
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
148
frontend/src/utils/wallet.js
Normal file
148
frontend/src/utils/wallet.js
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
import axios from '../api/axios';
|
||||||
|
import { ethers } from 'ethers';
|
||||||
|
import { SiweMessage } from 'siwe';
|
||||||
|
|
||||||
|
export const connectWallet = async () => {
|
||||||
|
try {
|
||||||
|
console.log('Starting wallet connection...');
|
||||||
|
|
||||||
|
// Проверяем наличие MetaMask или другого Ethereum провайдера
|
||||||
|
if (!window.ethereum) {
|
||||||
|
console.error('No Ethereum provider (like MetaMask) detected!');
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: 'Не найден кошелек MetaMask или другой Ethereum провайдер. Пожалуйста, установите расширение MetaMask.'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('MetaMask detected, requesting accounts...');
|
||||||
|
|
||||||
|
// Запрашиваем доступ к аккаунтам
|
||||||
|
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
|
||||||
|
console.log('Got accounts:', accounts);
|
||||||
|
|
||||||
|
if (!accounts || accounts.length === 0) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: 'Не удалось получить доступ к аккаунтам. Пожалуйста, разрешите доступ в MetaMask.'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Берем первый аккаунт в списке
|
||||||
|
const address = accounts[0];
|
||||||
|
// Нормализуем адрес (приводим к нижнему регистру для последующих сравнений)
|
||||||
|
const normalizedAddress = ethers.utils.getAddress(address);
|
||||||
|
console.log('Normalized address:', normalizedAddress);
|
||||||
|
|
||||||
|
// Запрашиваем nonce с сервера
|
||||||
|
console.log('Requesting nonce...');
|
||||||
|
const nonceResponse = await axios.get(`/api/auth/nonce?address=${normalizedAddress}`);
|
||||||
|
const nonce = nonceResponse.data.nonce;
|
||||||
|
console.log('Got nonce:', nonce);
|
||||||
|
|
||||||
|
if (!nonce) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: 'Не удалось получить nonce от сервера.'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Создаем провайдер Ethers
|
||||||
|
const provider = new ethers.providers.Web3Provider(window.ethereum);
|
||||||
|
const signer = provider.getSigner();
|
||||||
|
|
||||||
|
// Создаем сообщение для подписи
|
||||||
|
const domain = window.location.host;
|
||||||
|
const origin = window.location.origin;
|
||||||
|
|
||||||
|
// Создаем SIWE сообщение
|
||||||
|
const message = new SiweMessage({
|
||||||
|
domain,
|
||||||
|
address: normalizedAddress,
|
||||||
|
statement: 'Sign in with Ethereum to the app.',
|
||||||
|
uri: origin,
|
||||||
|
version: '1',
|
||||||
|
chainId: 1, // Ethereum mainnet
|
||||||
|
nonce: nonce,
|
||||||
|
issuedAt: new Date().toISOString(),
|
||||||
|
resources: [`${origin}/api/auth/verify`]
|
||||||
|
});
|
||||||
|
|
||||||
|
// Получаем строку сообщения для подписи
|
||||||
|
const messageToSign = message.prepareMessage();
|
||||||
|
console.log('SIWE message:', messageToSign);
|
||||||
|
|
||||||
|
// Запрашиваем подпись
|
||||||
|
console.log('Requesting signature...');
|
||||||
|
const signature = await signer.signMessage(messageToSign);
|
||||||
|
|
||||||
|
if (!signature) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: 'Подпись не была получена. Пожалуйста, подпишите сообщение в MetaMask.'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Got signature:', signature);
|
||||||
|
|
||||||
|
// Отправляем верификацию на сервер
|
||||||
|
console.log('Sending verification request...');
|
||||||
|
const verifyResponse = await axios.post('/api/auth/verify', {
|
||||||
|
address: normalizedAddress,
|
||||||
|
signature,
|
||||||
|
nonce
|
||||||
|
});
|
||||||
|
|
||||||
|
// Обновляем интерфейс для отображения подключенного состояния
|
||||||
|
document.body.classList.add('wallet-connected');
|
||||||
|
|
||||||
|
// Обновляем отображение адреса кошелька в UI
|
||||||
|
const authDisplayEl = document.getElementById('auth-display');
|
||||||
|
if (authDisplayEl) {
|
||||||
|
const shortAddress = `${normalizedAddress.substring(0, 6)}...${normalizedAddress.substring(normalizedAddress.length - 4)}`;
|
||||||
|
authDisplayEl.innerHTML = `Кошелек: <strong>${shortAddress}</strong>`;
|
||||||
|
authDisplayEl.style.display = 'inline-block';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Скрываем кнопки авторизации и показываем кнопку выхода
|
||||||
|
const authButtonsEl = document.getElementById('auth-buttons');
|
||||||
|
const logoutButtonEl = document.getElementById('logout-button');
|
||||||
|
|
||||||
|
if (authButtonsEl) authButtonsEl.style.display = 'none';
|
||||||
|
if (logoutButtonEl) logoutButtonEl.style.display = 'inline-block';
|
||||||
|
|
||||||
|
console.log('Verification response:', verifyResponse.data);
|
||||||
|
|
||||||
|
if (verifyResponse.data.success) {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
address: normalizedAddress,
|
||||||
|
userId: verifyResponse.data.userId,
|
||||||
|
isAdmin: verifyResponse.data.isAdmin
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: verifyResponse.data.error || 'Ошибка верификации на сервере.'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error connecting wallet:', error);
|
||||||
|
|
||||||
|
// Формируем понятное сообщение об ошибке
|
||||||
|
let errorMessage = 'Произошла ошибка при подключении кошелька.';
|
||||||
|
|
||||||
|
if (error.code === 4001) {
|
||||||
|
errorMessage = 'Вы отклонили запрос на подпись в MetaMask.';
|
||||||
|
} else if (error.response && error.response.data && error.response.data.error) {
|
||||||
|
errorMessage = error.response.data.error;
|
||||||
|
} else if (error.message) {
|
||||||
|
errorMessage = error.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: errorMessage
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user