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

This commit is contained in:
2025-04-22 17:41:05 +03:00
parent 6787ecf9ba
commit 5e062c8d9b
9 changed files with 548 additions and 251 deletions

View File

@@ -4,6 +4,7 @@ const { ethers } = require('ethers');
const crypto = require('crypto');
const { processMessage } = require('./ai-assistant'); // Используем AI Assistant
const verificationService = require('./verification-service'); // Используем сервис верификации
const identityService = require('./identity-service'); // <-- ДОБАВЛЕН ИМПОРТ
const ADMIN_CONTRACTS = [
{ address: '0xd95a45fc46a7300e6022885afec3d618d7d3f27c', network: 'eth' },
@@ -385,13 +386,22 @@ class AuthService {
// Получение связанного кошелька
async getLinkedWallet(userId) {
const result = await db.query(
`SELECT provider_id as address
FROM user_identities
WHERE user_id = $1 AND provider = 'wallet'`,
[userId]
);
return result.rows[0]?.address;
logger.info(`[getLinkedWallet] Called with userId: ${userId} (Type: ${typeof userId})`);
try {
const result = await db.query(
`SELECT provider_id as address
FROM user_identities
WHERE user_id = $1 AND provider = 'wallet'`,
[userId]
);
logger.info(`[getLinkedWallet] DB query result for userId ${userId}:`, result.rows);
const address = result.rows[0]?.address;
logger.info(`[getLinkedWallet] Returning address: ${address} for userId ${userId}`);
return address;
} catch (error) {
logger.error(`[getLinkedWallet] Error fetching linked wallet for userId ${userId}:`, error);
return undefined;
}
}
/**
@@ -774,6 +784,110 @@ class AuthService {
throw error;
}
}
/**
* Обрабатывает успешную верификацию Email.
* Находит или создает пользователя, связывает email, проверяет роль админа.
* @param {string} email - Верифицированный email.
* @param {object} session - Объект сессии запроса.
* @returns {Promise<{userId: number, email: string, role: string, isNewUser: boolean}>}
*/
async handleEmailVerification(email, session) {
const normalizedEmail = email.toLowerCase();
let userId;
let isNewUser = false;
let userRole = 'user'; // Роль по умолчанию
try {
// 1. Определить пользователя (существующий по email/сессии или новый)
if (session.authenticated && session.userId) {
// Используем уже аутентифицированного пользователя
userId = session.userId;
logger.info(`[handleEmailVerification] Using authenticated user ${userId}`);
} else {
// Ищем существующего пользователя по email
const existingUser = await identityService.findUserByIdentity('email', normalizedEmail);
if (existingUser) {
userId = existingUser.id;
logger.info(`[handleEmailVerification] Found existing user ${userId} by email ${normalizedEmail}`);
} else if (session.tempUserId) {
// Используем временного пользователя, если есть
userId = session.tempUserId;
logger.info(`[handleEmailVerification] Using temporary user ${userId}`);
} else {
// Создаем нового пользователя
const newUserResult = await db.query('INSERT INTO users (role) VALUES ($1) RETURNING id', [
'user',
]);
userId = newUserResult.rows[0].id;
isNewUser = true;
logger.info(`[handleEmailVerification] Created new user ${userId}`);
}
}
// 2. Связать email с пользователем (если еще не связан)
await identityService.saveIdentity(userId, 'email', normalizedEmail, true);
logger.info(`[handleEmailVerification] Ensured email identity ${normalizedEmail} for user ${userId}`);
// 3. Связать гостевые ID (если есть)
if (session.guestId) {
await identityService.saveIdentity(userId, 'guest', session.guestId, true);
}
if (session.previousGuestId && session.previousGuestId !== session.guestId) {
await identityService.saveIdentity(userId, 'guest', session.previousGuestId, true);
}
// 4. Проверить роль на основе привязанного кошелька
try {
const linkedWallet = await this.getLinkedWallet(userId);
if (linkedWallet && linkedWallet.provider_id) {
logger.info(`[handleEmailVerification] Found linked wallet ${linkedWallet.provider_id}. Checking role...`);
const isAdmin = await this.checkAdminRole(linkedWallet.provider_id);
userRole = isAdmin ? 'admin' : 'user';
logger.info(`[handleEmailVerification] Role determined as: ${userRole}`);
// Опционально: Обновить роль в таблице users
const currentUser = await db.query('SELECT role FROM users WHERE id = $1', [userId]);
if (currentUser.rows.length > 0 && currentUser.rows[0].role !== userRole) {
await db.query('UPDATE users SET role = $1 WHERE id = $2', [userRole, userId]);
logger.info(`[handleEmailVerification] Updated user role in DB to ${userRole}`);
}
} else {
logger.info(`[handleEmailVerification] No linked wallet found. Role remains 'user'.`);
// Если кошелька нет, проверяем текущую роль из базы (на случай, если она была admin ранее)
const currentUser = await db.query('SELECT role FROM users WHERE id = $1', [userId]);
if (currentUser.rows.length > 0) {
userRole = currentUser.rows[0].role;
}
}
} catch (roleCheckError) {
logger.error(`[handleEmailVerification] Error checking admin role:`, roleCheckError);
// В случае ошибки берем текущую роль из базы или оставляем 'user'
try {
const currentUser = await db.query('SELECT role FROM users WHERE id = $1', [userId]);
if (currentUser.rows.length > 0) {
userRole = currentUser.rows[0].role;
}
} catch (dbError) {
logger.error('Error fetching current user role after role check error:', dbError);
}
}
// Очистка временных данных из сессии
delete session.tempUserId;
delete session.pendingEmail;
return {
userId,
email: normalizedEmail,
role: userRole,
isNewUser,
};
} catch (error) {
logger.error('Error in handleEmailVerification:', error);
throw new Error('Ошибка обработки верификации Email');
}
}
}
// Создаем и экспортируем единственный экземпляр

View File

@@ -161,6 +161,31 @@ class EmailAuth {
await authService.identityService.saveIdentity(finalUserId, 'email', email, true);
logger.info(`[checkEmailVerification] Added email identity ${email} for user ${finalUserId}`);
// ----> НАЧАЛО: Проверка роли на основе привязанного кошелька
let userRole = 'user'; // Роль по умолчанию
try {
const linkedWallet = await authService.getLinkedWallet(finalUserId);
if (linkedWallet) {
logger.info(`[checkEmailVerification] Found linked wallet ${linkedWallet} for user ${finalUserId}. Checking admin role...`);
const isAdmin = await authService.checkAdminRole(linkedWallet);
userRole = isAdmin ? 'admin' : 'user';
logger.info(`[checkEmailVerification] Role for user ${finalUserId} determined as: ${userRole}`);
// Опционально: Обновить роль в таблице users, если она отличается
const currentUser = await db.query('SELECT role FROM users WHERE id = $1', [finalUserId]);
if (currentUser.rows.length > 0 && currentUser.rows[0].role !== userRole) {
await db.query('UPDATE users SET role = $1 WHERE id = $2', [userRole, finalUserId]);
logger.info(`[checkEmailVerification] Updated user role in DB to ${userRole}`);
}
} else {
logger.info(`[checkEmailVerification] No linked wallet found for user ${finalUserId}. Role remains 'user'.`);
}
} catch (roleCheckError) {
logger.error(`[checkEmailVerification] Error checking admin role for user ${finalUserId}:`, roleCheckError);
// В случае ошибки оставляем роль 'user'
}
// ----> КОНЕЦ: Проверка роли
// Если есть гостевой ID, добавляем его тоже
if (session.guestId) {
await authService.identityService.saveIdentity(finalUserId, 'guest', session.guestId, true);
@@ -179,6 +204,7 @@ class EmailAuth {
verified: true,
userId: finalUserId,
email: email,
role: userRole,
};
} catch (error) {
logger.error('Error checking email verification:', error);

View File

@@ -238,6 +238,49 @@ class IdentityService {
}
}
/**
* Находит конкретный идентификатор пользователя по его типу.
* Возвращает первую найденную запись.
* @param {number} userId - ID пользователя
* @param {string} provider - Тип идентификатора (например, 'wallet', 'email')
* @returns {Promise<object|null>} - Объект идентификатора (содержит provider_id) или null
*/
async findIdentity(userId, provider) {
try {
if (!userId || !provider) {
logger.warn(`[IdentityService] Missing parameters for findIdentity: userId=${userId}, provider=${provider}`);
return null;
}
// Нормализуем провайдера
const normalizedProvider = provider.toLowerCase();
const result = await db.query(
`SELECT provider, provider_id, created_at, updated_at
FROM user_identities
WHERE user_id = $1 AND provider = $2
LIMIT 1`,
[userId, normalizedProvider]
);
if (result.rows.length === 0) {
logger.info(`[IdentityService] No ${normalizedProvider} identity found for user ${userId}`);
return null;
}
logger.info(
`[IdentityService] Found ${normalizedProvider} identity for user ${userId}: ${result.rows[0].provider_id}`
);
return result.rows[0]; // Возвращаем всю строку (включая provider_id)
} catch (error) {
logger.error(
`[IdentityService] Error finding ${provider} identity for user ${userId}:`,
error
);
return null;
}
}
/**
* Сохраняет идентификаторы из сессии для пользователя
* @param {object} session - Объект сессии

View File

@@ -41,6 +41,7 @@ async function getBot() {
const providerId = verification.provider_id;
const linkedUserId = verification.user_id; // Получаем связанный userId если он есть
let userId;
let userRole = 'user'; // Роль по умолчанию
// Отмечаем код как использованный
await db.query('UPDATE verification_codes SET used = true WHERE id = $1', [
@@ -134,35 +135,95 @@ async function getBot() {
}
}
// Логируем guestId перед обновлением сессии
logger.info(`[telegramBot] Attempting to update session for guestId: ${providerId}`);
// ----> НАЧАЛО: Проверка роли на основе привязанного кошелька <----
if (userId) { // Убедимся, что userId определен
logger.info(`[TelegramBot] Checking linked wallet for determined userId: ${userId} (Type: ${typeof userId})`);
try {
const linkedWallet = await authService.getLinkedWallet(userId);
if (linkedWallet) {
logger.info(`[TelegramBot] Found linked wallet ${linkedWallet} for user ${userId}. Checking role...`);
const isAdmin = await authService.checkAdminRole(linkedWallet);
userRole = isAdmin ? 'admin' : 'user';
logger.info(`[TelegramBot] Role for user ${userId} determined as: ${userRole}`);
// Обновляем сессию в базе данных
const updateResult = await db.query(
`UPDATE session
SET sess = (sess::jsonb || $1::jsonb)::json
WHERE sess::jsonb @> $2::jsonb`,
[
JSON.stringify({
userId: userId.toString(),
authenticated: true,
authType: 'telegram',
telegramId: ctx.from.id.toString(),
// Добавляем имя и юзернейм из Telegram
telegramUsername: ctx.from.username,
telegramFirstName: ctx.from.first_name,
}),
JSON.stringify({ guestId: providerId }),
]
);
// Логируем результат обновления сессии
if (updateResult.rowCount > 0) {
logger.info(`Session updated successfully for guestId: ${providerId}, userId: ${userId}`);
// Опционально: Обновить роль в таблице users
const currentUser = await db.query('SELECT role FROM users WHERE id = $1', [userId]);
if (currentUser.rows.length > 0 && currentUser.rows[0].role !== userRole) {
await db.query('UPDATE users SET role = $1 WHERE id = $2', [userRole, userId]);
logger.info(`[TelegramBot] Updated user role in DB to ${userRole}`);
}
} else {
logger.info(`[TelegramBot] No linked wallet found for user ${userId}. Checking current DB role.`);
// Если кошелька нет, берем текущую роль из базы
const currentUser = await db.query('SELECT role FROM users WHERE id = $1', [userId]);
if (currentUser.rows.length > 0) {
userRole = currentUser.rows[0].role;
}
}
} catch (roleCheckError) {
logger.error(`[TelegramBot] Error checking admin role for user ${userId}:`, roleCheckError);
// В случае ошибки берем роль из базы или оставляем 'user'
try {
const currentUser = await db.query('SELECT role FROM users WHERE id = $1', [userId]);
if (currentUser.rows.length > 0) { userRole = currentUser.rows[0].role; }
} catch (dbError) { /* ignore */ }
}
} else {
logger.warn(
`Session update failed: No session found or updated for guestId: ${providerId}. User ${userId} authenticated via Telegram, but web session might not reflect it.`
logger.error('[TelegramBot] Cannot check role because userId is undefined!');
}
// ----> КОНЕЦ: Проверка роли <----
// Логируем userId перед обновлением сессии
logger.info(`[telegramBot] Attempting to update session for userId: ${userId}`);
// Находим последнюю активную сессию для данного userId
let activeSessionId = null;
try {
// Ищем сессию, где есть userId и она не истекла (проверка expires_at)
// Сортируем по expires_at DESC чтобы взять самую "свежую", если их несколько
const sessionResult = await db.query(
`SELECT sid FROM session
WHERE sess ->> 'userId' = $1
AND expire > NOW()
ORDER BY expire DESC
LIMIT 1`,
[userId?.toString()] // Используем optional chaining и преобразуем в строку
);
if (sessionResult.rows.length > 0) {
activeSessionId = sessionResult.rows[0].sid;
logger.info(`[telegramBot] Found active session ID ${activeSessionId} for user ${userId}`);
// Обновляем найденную сессию в базе данных, добавляя/перезаписывая данные Telegram
const updateResult = await db.query(
`UPDATE session
SET sess = (sess::jsonb || $1::jsonb)::json
WHERE sid = $2`,
[
JSON.stringify({
// authenticated: true, // Не перезаписываем, т.к. сессия уже должна быть аутентифицирована
authType: 'telegram', // Обновляем тип аутентификации
telegramId: ctx.from.id.toString(),
telegramUsername: ctx.from.username,
telegramFirstName: ctx.from.first_name,
role: userRole, // Записываем определенную роль
// userId: userId?.toString() // userId уже должен быть в сессии
}),
activeSessionId // Обновляем по найденному session ID
]
);
if (updateResult.rowCount > 0) {
logger.info(`[telegramBot] Session ${activeSessionId} updated successfully with Telegram data for user ${userId}`);
} else {
logger.warn(`[telegramBot] Session update query executed but did not update rows for sid: ${activeSessionId}. This might indicate a concurrency issue or incorrect sid.`);
}
} else {
logger.warn(`[telegramBot] No active web session found for userId: ${userId}. Telegram is linked, but the user might need to refresh their browser session.`);
}
} catch(sessionError) {
logger.error(`[telegramBot] Error finding or updating session for userId ${userId}:`, sessionError);
}
// Отправляем сообщение об успешной аутентификации