ваше сообщение коммита
This commit is contained in:
@@ -12,9 +12,10 @@ class AIAssistant {
|
||||
|
||||
// Создание экземпляра ChatOllama с нужными параметрами
|
||||
createChat(language = 'ru') {
|
||||
const systemPrompt = language === 'ru'
|
||||
? 'Вы - полезный ассистент. Отвечайте на русском языке.'
|
||||
: 'You are a helpful assistant. Respond in English.';
|
||||
const systemPrompt =
|
||||
language === 'ru'
|
||||
? 'Вы - полезный ассистент. Отвечайте на русском языке.'
|
||||
: 'You are a helpful assistant. Respond in English.';
|
||||
|
||||
return new ChatOllama({
|
||||
baseUrl: this.baseUrl,
|
||||
@@ -22,7 +23,7 @@ class AIAssistant {
|
||||
system: systemPrompt,
|
||||
temperature: 0.7,
|
||||
maxTokens: 1000,
|
||||
timeout: 30000 // 30 секунд таймаут
|
||||
timeout: 30000, // 30 секунд таймаут
|
||||
});
|
||||
}
|
||||
|
||||
@@ -36,14 +37,12 @@ class AIAssistant {
|
||||
async getResponse(message, language = 'auto') {
|
||||
try {
|
||||
console.log('getResponse called with:', { message, language });
|
||||
|
||||
|
||||
// Определяем язык, если не указан явно
|
||||
const detectedLanguage = language === 'auto'
|
||||
? this.detectLanguage(message)
|
||||
: language;
|
||||
const detectedLanguage = language === 'auto' ? this.detectLanguage(message) : language;
|
||||
|
||||
console.log('Detected language:', detectedLanguage);
|
||||
|
||||
|
||||
// Сначала пробуем прямой API запрос
|
||||
try {
|
||||
console.log('Trying direct API request...');
|
||||
@@ -67,7 +66,7 @@ class AIAssistant {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error in getResponse:', error);
|
||||
return "Извините, я не смог обработать ваш запрос. Пожалуйста, попробуйте позже.";
|
||||
return 'Извините, я не смог обработать ваш запрос. Пожалуйста, попробуйте позже.';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,10 +74,11 @@ class AIAssistant {
|
||||
async fallbackRequest(message, language) {
|
||||
try {
|
||||
console.log('Using fallback request method with:', { message, language });
|
||||
|
||||
const systemPrompt = language === 'ru'
|
||||
? 'Вы - полезный ассистент. Отвечайте на русском языке.'
|
||||
: 'You are a helpful assistant. Respond in English.';
|
||||
|
||||
const systemPrompt =
|
||||
language === 'ru'
|
||||
? 'Вы - полезный ассистент. Отвечайте на русском языке.'
|
||||
: 'You are a helpful assistant. Respond in English.';
|
||||
|
||||
console.log('Sending request to Ollama API...');
|
||||
const response = await fetch(`${this.baseUrl}/api/generate`, {
|
||||
@@ -91,15 +91,15 @@ class AIAssistant {
|
||||
stream: false,
|
||||
options: {
|
||||
temperature: 0.7,
|
||||
num_predict: 1000
|
||||
}
|
||||
num_predict: 1000,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
|
||||
const data = await response.json();
|
||||
console.log('Ollama API response:', data);
|
||||
return data.response;
|
||||
|
||||
@@ -6,15 +6,13 @@ const { processMessage } = require('./ai-assistant'); // Используем AI
|
||||
const verificationService = require('./verification-service'); // Используем сервис верификации
|
||||
|
||||
const ADMIN_CONTRACTS = [
|
||||
{ address: "0xd95a45fc46a7300e6022885afec3d618d7d3f27c", network: "eth" },
|
||||
{ address: "0x4B294265720B09ca39BFBA18c7E368413c0f68eB", network: "bsc" },
|
||||
{ address: "0xdce769b847a0a697239777d0b1c7dd33b6012ba0", network: "arbitrum" },
|
||||
{ address: "0x351f59de4fedbdf7601f5592b93db3b9330c1c1d", network: "polygon" }
|
||||
{ address: '0xd95a45fc46a7300e6022885afec3d618d7d3f27c', network: 'eth' },
|
||||
{ address: '0x4B294265720B09ca39BFBA18c7E368413c0f68eB', network: 'bsc' },
|
||||
{ address: '0xdce769b847a0a697239777d0b1c7dd33b6012ba0', network: 'arbitrum' },
|
||||
{ address: '0x351f59de4fedbdf7601f5592b93db3b9330c1c1d', network: 'polygon' },
|
||||
];
|
||||
|
||||
const ERC20_ABI = [
|
||||
"function balanceOf(address owner) view returns (uint256)"
|
||||
];
|
||||
const ERC20_ABI = ['function balanceOf(address owner) view returns (uint256)'];
|
||||
|
||||
class AuthService {
|
||||
constructor() {
|
||||
@@ -22,7 +20,7 @@ class AuthService {
|
||||
eth: new ethers.JsonRpcProvider(process.env.RPC_URL_ETH),
|
||||
polygon: new ethers.JsonRpcProvider(process.env.RPC_URL_POLYGON),
|
||||
bsc: new ethers.JsonRpcProvider(process.env.RPC_URL_BSC),
|
||||
arbitrum: new ethers.JsonRpcProvider(process.env.RPC_URL_ARBITRUM)
|
||||
arbitrum: new ethers.JsonRpcProvider(process.env.RPC_URL_ARBITRUM),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -30,13 +28,13 @@ class AuthService {
|
||||
async verifySignature(message, signature, address) {
|
||||
try {
|
||||
if (!message || !signature || !address) return false;
|
||||
|
||||
|
||||
// Нормализуем входящий адрес
|
||||
const normalizedAddress = ethers.getAddress(address).toLowerCase();
|
||||
|
||||
|
||||
// Восстанавливаем адрес из подписи
|
||||
const recoveredAddress = ethers.verifyMessage(message, signature);
|
||||
|
||||
|
||||
// Сравниваем нормализованные адреса
|
||||
return ethers.getAddress(recoveredAddress).toLowerCase() === normalizedAddress;
|
||||
} catch (error) {
|
||||
@@ -54,20 +52,23 @@ class AuthService {
|
||||
try {
|
||||
// Нормализуем адрес - всегда приводим к нижнему регистру
|
||||
const normalizedAddress = ethers.getAddress(address).toLowerCase();
|
||||
|
||||
|
||||
// Ищем пользователя по адресу в таблице user_identities
|
||||
const userResult = await db.query(`
|
||||
const userResult = await db.query(
|
||||
`
|
||||
SELECT u.* FROM users u
|
||||
JOIN user_identities ui ON u.id = ui.user_id
|
||||
WHERE ui.provider = 'wallet' AND ui.provider_id = $1
|
||||
`, [normalizedAddress]);
|
||||
|
||||
`,
|
||||
[normalizedAddress]
|
||||
);
|
||||
|
||||
if (userResult.rows.length > 0) {
|
||||
const user = userResult.rows[0];
|
||||
|
||||
|
||||
// Проверяем роль администратора при каждой аутентификации
|
||||
const isAdmin = await this.checkAdminRole(normalizedAddress);
|
||||
|
||||
|
||||
// Если статус админа изменился, обновляем роль в базе данных
|
||||
if (user.role === 'admin' && !isAdmin) {
|
||||
await db.query('UPDATE users SET role = $1 WHERE id = $2', ['user', user.id]);
|
||||
@@ -78,37 +79,38 @@ class AuthService {
|
||||
logger.info(`Updated user ${user.id} role to admin (admin tokens found)`);
|
||||
return { userId: user.id, isAdmin: true };
|
||||
}
|
||||
|
||||
return {
|
||||
userId: user.id,
|
||||
isAdmin: user.role === 'admin'
|
||||
|
||||
return {
|
||||
userId: user.id,
|
||||
isAdmin: user.role === 'admin',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Если пользователь не найден, создаем нового
|
||||
const newUserResult = await db.query(
|
||||
'INSERT INTO users (role) VALUES ($1) RETURNING id',
|
||||
['user']
|
||||
);
|
||||
|
||||
const newUserResult = await db.query('INSERT INTO users (role) VALUES ($1) RETURNING id', [
|
||||
'user',
|
||||
]);
|
||||
|
||||
const userId = newUserResult.rows[0].id;
|
||||
|
||||
|
||||
// Добавляем идентификатор кошелька (всегда в нижнем регистре)
|
||||
await db.query(
|
||||
'INSERT INTO user_identities (user_id, provider, provider_id) VALUES ($1, $2, $3)',
|
||||
[userId, 'wallet', normalizedAddress]
|
||||
);
|
||||
|
||||
|
||||
// Проверяем, есть ли у пользователя роль админа
|
||||
const isAdmin = await this.checkAdminRole(normalizedAddress);
|
||||
logger.info(`New user ${userId} role check result: ${isAdmin ? 'admin' : 'user'}`);
|
||||
|
||||
|
||||
// Если у пользователя есть админские токены, обновляем его роль
|
||||
if (isAdmin) {
|
||||
await db.query('UPDATE users SET role = $1 WHERE id = $2', ['admin', userId]);
|
||||
logger.info(`New user ${userId} with wallet ${normalizedAddress} automatically granted admin role`);
|
||||
logger.info(
|
||||
`New user ${userId} with wallet ${normalizedAddress} automatically granted admin role`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return { userId, isAdmin };
|
||||
} catch (error) {
|
||||
logger.error('Error finding or creating user:', error);
|
||||
@@ -123,13 +125,13 @@ class AuthService {
|
||||
*/
|
||||
async checkAdminRole(address) {
|
||||
if (!address) return false;
|
||||
|
||||
|
||||
logger.info(`Checking admin role for address: ${address}`);
|
||||
let foundTokens = false;
|
||||
let errorCount = 0;
|
||||
const balances = {};
|
||||
const totalNetworks = ADMIN_CONTRACTS.length;
|
||||
|
||||
|
||||
// Создаем массив промисов для параллельной проверки балансов
|
||||
const checkPromises = ADMIN_CONTRACTS.map(async (contract) => {
|
||||
try {
|
||||
@@ -140,83 +142,83 @@ class AuthService {
|
||||
errorCount++;
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// Проверяем доступность провайдера
|
||||
try {
|
||||
// Проверка доступности сети с таймаутом
|
||||
const networkCheckPromise = provider.getNetwork();
|
||||
const timeoutPromise = new Promise((_, reject) =>
|
||||
const timeoutPromise = new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Network check timeout')), 3000)
|
||||
);
|
||||
|
||||
|
||||
await Promise.race([networkCheckPromise, timeoutPromise]);
|
||||
} catch (networkError) {
|
||||
logger.error(`Provider for ${contract.network} is not available: ${networkError.message}`);
|
||||
logger.error(
|
||||
`Provider for ${contract.network} is not available: ${networkError.message}`
|
||||
);
|
||||
balances[contract.network] = 'Error: Network unavailable';
|
||||
errorCount++;
|
||||
return null;
|
||||
}
|
||||
|
||||
const tokenContract = new ethers.Contract(
|
||||
contract.address,
|
||||
ERC20_ABI,
|
||||
provider
|
||||
);
|
||||
|
||||
|
||||
const tokenContract = new ethers.Contract(contract.address, ERC20_ABI, provider);
|
||||
|
||||
// Создаем промис с таймаутом
|
||||
const balancePromise = tokenContract.balanceOf(address);
|
||||
const timeoutPromise = new Promise((_, reject) =>
|
||||
const timeoutPromise = new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Timeout')), 3000)
|
||||
);
|
||||
|
||||
|
||||
// Ждем первый выполненный промис
|
||||
const balance = await Promise.race([balancePromise, timeoutPromise]);
|
||||
const formattedBalance = ethers.formatUnits(balance, 18);
|
||||
balances[contract.network] = formattedBalance;
|
||||
|
||||
|
||||
logger.info(`Token balance on ${contract.network}:`, {
|
||||
address,
|
||||
contract: contract.address,
|
||||
balance: formattedBalance,
|
||||
hasTokens: balance > 0
|
||||
hasTokens: balance > 0,
|
||||
});
|
||||
|
||||
if (parseFloat(formattedBalance) > 0) {
|
||||
logger.info(`Found admin tokens on ${contract.network}`);
|
||||
foundTokens = true;
|
||||
}
|
||||
|
||||
|
||||
return { network: contract.network, balance: formattedBalance };
|
||||
} catch (error) {
|
||||
logger.error(`Error checking balance in ${contract.network}:`, {
|
||||
address,
|
||||
contract: contract.address,
|
||||
error: error.message || 'Unknown error'
|
||||
error: error.message || 'Unknown error',
|
||||
});
|
||||
balances[contract.network] = 'Error';
|
||||
errorCount++;
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Ждем выполнения всех проверок
|
||||
await Promise.all(checkPromises);
|
||||
|
||||
|
||||
// Если все запросы завершились с ошибкой, считаем, что проверка не удалась
|
||||
if (errorCount === totalNetworks) {
|
||||
logger.error(`All network checks for ${address} failed. Cannot verify admin status.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (foundTokens) {
|
||||
logger.info(`Admin role summary for ${address}:`, {
|
||||
networks: Object.keys(balances).filter(net => balances[net] > 0 && balances[net] !== 'Error'),
|
||||
balances
|
||||
networks: Object.keys(balances).filter(
|
||||
(net) => balances[net] > 0 && balances[net] !== 'Error'
|
||||
),
|
||||
balances,
|
||||
});
|
||||
logger.info(`Admin role granted for ${address}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
logger.info(`Admin role denied - no tokens found for ${address}`);
|
||||
return false;
|
||||
}
|
||||
@@ -233,13 +235,13 @@ class AuthService {
|
||||
eth: '0',
|
||||
bsc: '0',
|
||||
arbitrum: '0',
|
||||
polygon: '0'
|
||||
polygon: '0',
|
||||
};
|
||||
}
|
||||
|
||||
const balances = {};
|
||||
const timeout = 3000; // 3 секунды таймаут
|
||||
|
||||
|
||||
for (const contract of ADMIN_CONTRACTS) {
|
||||
try {
|
||||
const provider = this.providers[contract.network];
|
||||
@@ -253,37 +255,35 @@ class AuthService {
|
||||
try {
|
||||
// Проверка доступности сети с таймаутом
|
||||
const networkCheckPromise = provider.getNetwork();
|
||||
const networkTimeoutPromise = new Promise((_, reject) =>
|
||||
const networkTimeoutPromise = new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Network check timeout')), timeout)
|
||||
);
|
||||
|
||||
|
||||
await Promise.race([networkCheckPromise, networkTimeoutPromise]);
|
||||
} catch (networkError) {
|
||||
logger.error(`Provider for ${contract.network} is not available: ${networkError.message}`);
|
||||
logger.error(
|
||||
`Provider for ${contract.network} is not available: ${networkError.message}`
|
||||
);
|
||||
balances[contract.network] = '0';
|
||||
continue;
|
||||
}
|
||||
|
||||
const tokenContract = new ethers.Contract(
|
||||
contract.address,
|
||||
ERC20_ABI,
|
||||
provider
|
||||
);
|
||||
const tokenContract = new ethers.Contract(contract.address, ERC20_ABI, provider);
|
||||
|
||||
// Создаем промис с таймаутом
|
||||
const balancePromise = tokenContract.balanceOf(address);
|
||||
const timeoutPromise = new Promise((_, reject) =>
|
||||
const timeoutPromise = new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Timeout')), timeout)
|
||||
);
|
||||
|
||||
// Ждем первый выполненный промис
|
||||
const balance = await Promise.race([balancePromise, timeoutPromise]);
|
||||
const formattedBalance = ethers.formatUnits(balance, 18);
|
||||
|
||||
|
||||
logger.info(`Token balance for ${address} on ${contract.network}:`, {
|
||||
contract: contract.address,
|
||||
balance: formattedBalance,
|
||||
timestamp: new Date().toISOString()
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
balances[contract.network] = formattedBalance;
|
||||
@@ -292,17 +292,17 @@ class AuthService {
|
||||
address,
|
||||
contract: contract.address,
|
||||
error: error.message || 'Unknown error',
|
||||
timestamp: new Date().toISOString()
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
balances[contract.network] = '0';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
logger.info(`Token balances fetched for ${address}:`, {
|
||||
...balances,
|
||||
timestamp: new Date().toISOString()
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
|
||||
return balances;
|
||||
}
|
||||
|
||||
@@ -318,26 +318,29 @@ class AuthService {
|
||||
session.userId = userId;
|
||||
session.authenticated = authenticated;
|
||||
session.authType = authType;
|
||||
|
||||
|
||||
// Сохраняем адрес кошелька если есть
|
||||
if (address) {
|
||||
session.address = address;
|
||||
}
|
||||
|
||||
|
||||
// Сохраняем сессию в БД
|
||||
const result = await db.query(
|
||||
`UPDATE session
|
||||
SET sess = $1
|
||||
WHERE sid = $2`,
|
||||
[JSON.stringify({
|
||||
userId,
|
||||
authenticated,
|
||||
authType,
|
||||
address,
|
||||
cookie: session.cookie
|
||||
}), session.id]
|
||||
[
|
||||
JSON.stringify({
|
||||
userId,
|
||||
authenticated,
|
||||
authType,
|
||||
address,
|
||||
cookie: session.cookie,
|
||||
}),
|
||||
session.id,
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error('Error creating session:', error);
|
||||
@@ -400,17 +403,19 @@ class AuthService {
|
||||
try {
|
||||
// Проверяем наличие связанного кошелька
|
||||
const wallet = await this.getLinkedWallet(userId);
|
||||
|
||||
|
||||
// Если кошелек не привязан, пользователь получает роль user
|
||||
// с базовым доступом к чату и истории сообщений
|
||||
if (!wallet) {
|
||||
logger.info(`No wallet linked for user ${userId}, assigning basic user role`);
|
||||
return 'user';
|
||||
}
|
||||
|
||||
|
||||
// Если есть кошелек, проверяем админские токены
|
||||
const isAdmin = await this.checkAdminRole(wallet);
|
||||
logger.info(`Role check for user ${userId} with wallet ${wallet}: ${isAdmin ? 'admin' : 'user'}`);
|
||||
logger.info(
|
||||
`Role check for user ${userId} with wallet ${wallet}: ${isAdmin ? 'admin' : 'user'}`
|
||||
);
|
||||
return isAdmin ? 'admin' : 'user';
|
||||
} catch (error) {
|
||||
logger.error('Error checking user role:', error);
|
||||
@@ -423,20 +428,17 @@ class AuthService {
|
||||
try {
|
||||
// Проверяем код через сервис верификации
|
||||
const result = await verificationService.verifyCode(code, 'email', null);
|
||||
|
||||
|
||||
if (!result.success) {
|
||||
return { verified: false };
|
||||
}
|
||||
|
||||
|
||||
const userId = result.userId;
|
||||
const email = result.providerId;
|
||||
|
||||
|
||||
// Проверяем, существует ли пользователь с таким email
|
||||
const userResult = await db.query(
|
||||
'SELECT * FROM users WHERE id = $1',
|
||||
[userId]
|
||||
);
|
||||
|
||||
const userResult = await db.query('SELECT * FROM users WHERE id = $1', [userId]);
|
||||
|
||||
if (userResult.rows.length === 0) {
|
||||
return { verified: false };
|
||||
}
|
||||
@@ -444,7 +446,7 @@ class AuthService {
|
||||
// Проверяем наличие кошелька и определяем роль
|
||||
const wallet = await this.getLinkedWallet(userId);
|
||||
let role = 'user'; // Базовая роль для доступа к чату
|
||||
|
||||
|
||||
if (wallet) {
|
||||
// Если есть кошелек, проверяем баланс токенов
|
||||
const isAdmin = await this.checkAdminRole(wallet);
|
||||
@@ -453,13 +455,13 @@ class AuthService {
|
||||
} else {
|
||||
logger.info(`User ${userId} has no wallet, using basic user role`);
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
verified: true,
|
||||
userId,
|
||||
email,
|
||||
role,
|
||||
wallet: wallet || null
|
||||
wallet: wallet || null,
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('Error checking email verification:', error);
|
||||
@@ -473,28 +475,30 @@ class AuthService {
|
||||
async verifyTelegramAuth(telegramId, verificationCode, session) {
|
||||
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`);
|
||||
|
||||
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
|
||||
isNewUser: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Если в сессии нет авторизованного пользователя, проверяем существующие идентификаторы
|
||||
// Проверяем, существует ли уже пользователь с таким Telegram ID
|
||||
const existingUserResult = await db.query(
|
||||
@@ -509,23 +513,26 @@ class AuthService {
|
||||
if (existingUserResult.rows.length > 0) {
|
||||
const existingUser = existingUserResult.rows[0];
|
||||
userId = existingUser.id;
|
||||
logger.info(`[verifyTelegramAuth] Found existing user ${userId} for Telegram ID ${telegramId}`);
|
||||
logger.info(
|
||||
`[verifyTelegramAuth] Found existing user ${userId} for Telegram ID ${telegramId}`
|
||||
);
|
||||
} else {
|
||||
// Создаем нового пользователя для нового telegramId
|
||||
const newUserResult = await db.query(
|
||||
'INSERT INTO users (role) VALUES ($1) RETURNING id',
|
||||
['user']
|
||||
);
|
||||
const newUserResult = await db.query('INSERT INTO users (role) VALUES ($1) RETURNING id', [
|
||||
'user',
|
||||
]);
|
||||
userId = newUserResult.rows[0].id;
|
||||
isNewUser = true;
|
||||
|
||||
|
||||
// Добавляем Telegram идентификатор
|
||||
await db.query(
|
||||
'INSERT INTO user_identities (user_id, provider, provider_id) VALUES ($1, $2, $3)',
|
||||
[userId, 'telegram', telegramId]
|
||||
);
|
||||
|
||||
logger.info(`[verifyTelegramAuth] Created new user ${userId} for Telegram ID ${telegramId}`);
|
||||
|
||||
logger.info(
|
||||
`[verifyTelegramAuth] Created new user ${userId} for Telegram ID ${telegramId}`
|
||||
);
|
||||
}
|
||||
|
||||
// Если есть гостевой ID в сессии, сохраняем его для нового пользователя
|
||||
@@ -542,7 +549,7 @@ class AuthService {
|
||||
userId,
|
||||
role: 'user',
|
||||
telegramId,
|
||||
isNewUser
|
||||
isNewUser,
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('[verifyTelegramAuth] Error:', error);
|
||||
@@ -553,30 +560,28 @@ class AuthService {
|
||||
// Добавляем псевдоним функции checkAdminRole для обратной совместимости
|
||||
async checkAdminTokens(address) {
|
||||
if (!address) return false;
|
||||
|
||||
|
||||
logger.info(`Checking admin tokens for address: ${address}`);
|
||||
|
||||
|
||||
try {
|
||||
const isAdmin = await this.checkAdminRole(address);
|
||||
|
||||
|
||||
// Обновляем роль пользователя в базе данных, если есть админские токены
|
||||
if (isAdmin) {
|
||||
try {
|
||||
// Находим userId по адресу
|
||||
const userResult = await db.query(`
|
||||
const userResult = await db.query(
|
||||
`
|
||||
SELECT u.id FROM users u
|
||||
JOIN user_identities ui ON u.id = ui.user_id
|
||||
WHERE ui.provider = 'wallet' AND ui.provider_id = $1`,
|
||||
[address.toLowerCase()]
|
||||
);
|
||||
|
||||
|
||||
if (userResult.rows.length > 0) {
|
||||
const userId = userResult.rows[0].id;
|
||||
// Обновляем роль пользователя
|
||||
await db.query(
|
||||
'UPDATE users SET role = $1 WHERE id = $2',
|
||||
['admin', userId]
|
||||
);
|
||||
await db.query('UPDATE users SET role = $1 WHERE id = $2', ['admin', userId]);
|
||||
logger.info(`Updated user ${userId} role to admin based on token holdings`);
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -586,26 +591,24 @@ class AuthService {
|
||||
} else {
|
||||
// Если пользователь не является администратором, сбрасываем роль на "user", если она была "admin"
|
||||
try {
|
||||
const userResult = await db.query(`
|
||||
const userResult = await db.query(
|
||||
`
|
||||
SELECT u.id, u.role FROM users u
|
||||
JOIN user_identities ui ON u.id = ui.user_id
|
||||
WHERE ui.provider = 'wallet' AND ui.provider_id = $1`,
|
||||
[address.toLowerCase()]
|
||||
);
|
||||
|
||||
|
||||
if (userResult.rows.length > 0 && userResult.rows[0].role === 'admin') {
|
||||
const userId = userResult.rows[0].id;
|
||||
await db.query(
|
||||
'UPDATE users SET role = $1 WHERE id = $2',
|
||||
['user', userId]
|
||||
);
|
||||
await db.query('UPDATE users SET role = $1 WHERE id = $2', ['user', userId]);
|
||||
logger.info(`Reset user ${userId} role from admin to user (no tokens found)`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error updating user role:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return isAdmin;
|
||||
} catch (error) {
|
||||
logger.error(`Error in checkAdminTokens: ${error.message}`);
|
||||
@@ -622,24 +625,21 @@ class AuthService {
|
||||
try {
|
||||
// Получаем все идентификаторы пользователя
|
||||
const identities = await this.getUserIdentities(userId);
|
||||
|
||||
|
||||
// Фильтруем только гостевые идентификаторы
|
||||
const guestIdentities = identities.filter(id => id.identity_type === 'guest');
|
||||
|
||||
const guestIdentities = identities.filter((id) => id.identity_type === 'guest');
|
||||
|
||||
// Если гостевых идентификаторов больше 3, удаляем старые
|
||||
if (guestIdentities.length > 3) {
|
||||
// Сортируем по дате создания (новые первые)
|
||||
guestIdentities.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
|
||||
|
||||
|
||||
// Оставляем только 3 последних идентификатора
|
||||
const identitiesToDelete = guestIdentities.slice(3);
|
||||
|
||||
|
||||
// Удаляем старые идентификаторы
|
||||
for (const identity of identitiesToDelete) {
|
||||
await db.query(
|
||||
'DELETE FROM user_identities WHERE id = $1',
|
||||
[identity.id]
|
||||
);
|
||||
await db.query('DELETE FROM user_identities WHERE id = $1', [identity.id]);
|
||||
logger.info(`Deleted old guest identity: ${identity.identity_value}`);
|
||||
}
|
||||
}
|
||||
@@ -676,9 +676,7 @@ class AuthService {
|
||||
try {
|
||||
const balance = await Promise.race([
|
||||
this.getTokenBalance(address, ADMIN_CONTRACTS.ARBITRUM),
|
||||
new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error('TIMEOUT')), timeout)
|
||||
)
|
||||
new Promise((_, reject) => setTimeout(() => reject(new Error('TIMEOUT')), timeout)),
|
||||
]);
|
||||
return { balance, hasTokens: balance > 0 };
|
||||
} catch (error) {
|
||||
@@ -697,10 +695,12 @@ class AuthService {
|
||||
async linkIdentity(userId, provider, providerId) {
|
||||
try {
|
||||
if (!userId || !provider || !providerId) {
|
||||
logger.warn(`[AuthService] Missing parameters for linkIdentity: userId=${userId}, provider=${provider}, providerId=${providerId}`);
|
||||
logger.warn(
|
||||
`[AuthService] Missing parameters for linkIdentity: userId=${userId}, provider=${provider}, providerId=${providerId}`
|
||||
);
|
||||
throw new Error('Missing parameters');
|
||||
}
|
||||
|
||||
|
||||
// Нормализуем значение идентификатора
|
||||
let normalizedProviderId = providerId;
|
||||
if (provider === 'wallet') {
|
||||
@@ -714,113 +714,63 @@ class AuthService {
|
||||
} else if (provider === 'email') {
|
||||
normalizedProviderId = providerId.toLowerCase();
|
||||
}
|
||||
|
||||
logger.info(`[AuthService] Linking identity ${provider}:${normalizedProviderId} to user ${userId}`);
|
||||
|
||||
|
||||
logger.info(
|
||||
`[AuthService] Linking identity ${provider}:${normalizedProviderId} to user ${userId}`
|
||||
);
|
||||
|
||||
// Проверяем, существует ли уже такой идентификатор
|
||||
const existingResult = await db.query(
|
||||
`SELECT user_id FROM user_identities WHERE provider = $1 AND provider_id = $2`,
|
||||
[provider, normalizedProviderId]
|
||||
);
|
||||
|
||||
|
||||
if (existingResult.rows.length > 0) {
|
||||
const existingUserId = existingResult.rows[0].user_id;
|
||||
|
||||
|
||||
// Если идентификатор уже принадлежит этому пользователю, ничего не делаем
|
||||
if (existingUserId === userId) {
|
||||
logger.info(`[AuthService] Identity ${provider}:${normalizedProviderId} already exists for user ${userId}`);
|
||||
logger.info(
|
||||
`[AuthService] Identity ${provider}:${normalizedProviderId} already exists for user ${userId}`
|
||||
);
|
||||
return { success: true, message: 'Identity already exists' };
|
||||
} else {
|
||||
// Если идентификатор принадлежит другому пользователю, возвращаем ошибку
|
||||
logger.warn(`[AuthService] Identity ${provider}:${normalizedProviderId} already belongs to user ${existingUserId}, not user ${userId}`);
|
||||
logger.warn(
|
||||
`[AuthService] Identity ${provider}:${normalizedProviderId} 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, normalizedProviderId]
|
||||
);
|
||||
|
||||
|
||||
// Проверяем и обновляем роль администратора, если это идентификатор кошелька
|
||||
let isAdmin = false;
|
||||
if (provider === 'wallet') {
|
||||
isAdmin = await this.checkAdminTokens(normalizedProviderId);
|
||||
|
||||
|
||||
// Обновляем роль пользователя в базе данных, если нужно
|
||||
if (isAdmin) {
|
||||
await db.query(
|
||||
'UPDATE users SET role = $1 WHERE id = $2',
|
||||
['admin', userId]
|
||||
);
|
||||
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}:${normalizedProviderId} successfully linked to user ${userId}`);
|
||||
|
||||
logger.info(
|
||||
`[AuthService] Identity ${provider}:${normalizedProviderId} 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработка гостевых сообщений после аутентификации
|
||||
* ПРИМЕЧАНИЕ: Эта функция оставлена для обратной совместимости.
|
||||
* Фактически все маршруты теперь используют версию функции из auth.js,
|
||||
* которая корректно обрабатывает сообщения для всех типов аутентификации.
|
||||
* @deprecated Используйте функцию linkGuestMessagesAfterAuth из routes/auth.js
|
||||
*/
|
||||
async linkGuestMessagesAfterAuth(userId, currentGuestId, previousGuestId) {
|
||||
try {
|
||||
logger.info(`[linkGuestMessagesAfterAuth] Starting for user ${userId} with guestId=${currentGuestId}`);
|
||||
|
||||
// Проверяем, есть ли идентификатор для обработки
|
||||
if (!currentGuestId) {
|
||||
logger.debug('[linkGuestMessagesAfterAuth] No guest ID to process');
|
||||
return { success: true, message: 'No guest ID to process' };
|
||||
}
|
||||
|
||||
// Проверяем, не привязаны ли уже эти гостевые сообщения к другому пользователю
|
||||
const existingMessagesCheck = await db.query(
|
||||
`SELECT DISTINCT user_id
|
||||
FROM messages
|
||||
WHERE guest_message_id IN (
|
||||
SELECT id FROM guest_messages WHERE guest_id = $1
|
||||
)`,
|
||||
[currentGuestId]
|
||||
logger.error(
|
||||
`[AuthService] Error linking identity ${provider}:${providerId} to user ${userId}:`,
|
||||
error
|
||||
);
|
||||
|
||||
if (existingMessagesCheck.rows.length > 0) {
|
||||
const existingUserId = existingMessagesCheck.rows[0].user_id;
|
||||
if (existingUserId !== userId) {
|
||||
logger.warn(`[linkGuestMessagesAfterAuth] Guest messages for ${currentGuestId} are already linked to user ${existingUserId}`);
|
||||
return {
|
||||
success: false,
|
||||
error: 'Guest messages are already linked to another user'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Используем ту же функцию processGuestMessages что и в auth.js
|
||||
const result = await processGuestMessages(userId, currentGuestId);
|
||||
logger.info(`[linkGuestMessagesAfterAuth] Guest messages processed: ${JSON.stringify(result)}`);
|
||||
|
||||
// Если есть предыдущий гостевой ID, обработаем и его
|
||||
if (previousGuestId && previousGuestId !== currentGuestId) {
|
||||
const prevResult = await processGuestMessages(userId, previousGuestId);
|
||||
logger.info(`[linkGuestMessagesAfterAuth] Previous guest messages processed: ${JSON.stringify(prevResult)}`);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
result: result
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('[linkGuestMessagesAfterAuth] Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -828,4 +778,4 @@ class AuthService {
|
||||
|
||||
// Создаем и экспортируем единственный экземпляр
|
||||
const authService = new AuthService();
|
||||
module.exports = authService;
|
||||
module.exports = authService;
|
||||
|
||||
@@ -15,7 +15,7 @@ class EmailAuth {
|
||||
if (!email || !email.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) {
|
||||
throw new Error('Некорректный формат email');
|
||||
}
|
||||
|
||||
|
||||
// Проверяем, существует ли пользователь с таким email
|
||||
const existingEmailUser = await db.query(
|
||||
`SELECT u.id FROM users u
|
||||
@@ -23,44 +23,47 @@ class EmailAuth {
|
||||
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}`);
|
||||
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']
|
||||
);
|
||||
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 в сессии
|
||||
session.pendingEmail = email.toLowerCase();
|
||||
|
||||
|
||||
// Создаем код через сервис верификации
|
||||
const verificationCode = await verificationService.createVerificationCode(
|
||||
'email',
|
||||
email.toLowerCase(),
|
||||
userId
|
||||
);
|
||||
|
||||
|
||||
// Отправляем код на email
|
||||
await this.emailBot.sendVerificationCode(email, verificationCode);
|
||||
|
||||
logger.info(`Generated verification code for Email auth for ${email} and sent to user's email`);
|
||||
|
||||
|
||||
logger.info(
|
||||
`Generated verification code for Email auth for ${email} and sent to user's email`
|
||||
);
|
||||
|
||||
return { success: true, verificationCode };
|
||||
} catch (error) {
|
||||
logger.error('Error in email auth initialization:', error);
|
||||
@@ -80,7 +83,7 @@ class EmailAuth {
|
||||
|
||||
// Проверяем код через сервис верификации
|
||||
const result = await verificationService.verifyCode(code, 'email', session.pendingEmail);
|
||||
|
||||
|
||||
if (!result.success) {
|
||||
// Используем сообщение об ошибке из сервиса верификации
|
||||
return { verified: false, message: result.error || 'Неверный код верификации' };
|
||||
@@ -93,28 +96,31 @@ class EmailAuth {
|
||||
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
|
||||
email: email,
|
||||
};
|
||||
}
|
||||
|
||||
// Если пользователь не авторизован, ищем всех пользователей с похожими идентификаторами
|
||||
const identities = {
|
||||
email: email,
|
||||
guest: session.guestId
|
||||
guest: session.guestId,
|
||||
};
|
||||
|
||||
const relatedUsers = await authService.identityService.findRelatedUsers(identities);
|
||||
logger.info(`[checkEmailVerification] Found ${relatedUsers.length} related users for identities:`, identities);
|
||||
logger.info(
|
||||
`[checkEmailVerification] Found ${relatedUsers.length} related users for identities:`,
|
||||
identities
|
||||
);
|
||||
|
||||
if (relatedUsers.length > 0) {
|
||||
// Берем первого найденного пользователя как основного
|
||||
@@ -124,13 +130,17 @@ class EmailAuth {
|
||||
// Мигрируем данные от остальных пользователей к основному
|
||||
for (const userId of relatedUsers.slice(1)) {
|
||||
await authService.identityService.migrateUserData(userId, finalUserId);
|
||||
logger.info(`[checkEmailVerification] Migrated data from user ${userId} to ${finalUserId}`);
|
||||
logger.info(
|
||||
`[checkEmailVerification] Migrated data from user ${userId} to ${finalUserId}`
|
||||
);
|
||||
}
|
||||
|
||||
// Если у нас есть временный пользователь, мигрируем его данные тоже
|
||||
if (session.tempUserId && !relatedUsers.includes(session.tempUserId)) {
|
||||
await authService.identityService.migrateUserData(session.tempUserId, finalUserId);
|
||||
logger.info(`[checkEmailVerification] Migrated temporary user ${session.tempUserId} to ${finalUserId}`);
|
||||
logger.info(
|
||||
`[checkEmailVerification] Migrated temporary user ${session.tempUserId} to ${finalUserId}`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Если связанных пользователей нет, используем временного или создаем нового
|
||||
@@ -154,7 +164,9 @@ class EmailAuth {
|
||||
// Если есть гостевой ID, добавляем его тоже
|
||||
if (session.guestId) {
|
||||
await authService.identityService.saveIdentity(finalUserId, 'guest', session.guestId, true);
|
||||
logger.info(`[checkEmailVerification] Added guest identity ${session.guestId} for user ${finalUserId}`);
|
||||
logger.info(
|
||||
`[checkEmailVerification] Added guest identity ${session.guestId} for user ${finalUserId}`
|
||||
);
|
||||
}
|
||||
|
||||
// Очищаем временные данные
|
||||
@@ -166,7 +178,7 @@ class EmailAuth {
|
||||
return {
|
||||
verified: true,
|
||||
userId: finalUserId,
|
||||
email: email
|
||||
email: email,
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('Error checking email verification:', error);
|
||||
@@ -177,4 +189,4 @@ class EmailAuth {
|
||||
|
||||
// Создаем и экспортируем единственный экземпляр
|
||||
const emailAuth = new EmailAuth();
|
||||
module.exports = emailAuth;
|
||||
module.exports = emailAuth;
|
||||
|
||||
@@ -19,8 +19,8 @@ const transporter = nodemailer.createTransport({
|
||||
maxConnections: 3,
|
||||
maxMessages: 5,
|
||||
tls: {
|
||||
rejectUnauthorized: false
|
||||
}
|
||||
rejectUnauthorized: false,
|
||||
},
|
||||
});
|
||||
|
||||
// Конфигурация для получения писем
|
||||
@@ -31,11 +31,11 @@ const imapConfig = {
|
||||
port: process.env.EMAIL_IMAP_PORT,
|
||||
tls: true,
|
||||
tlsOptions: { rejectUnauthorized: false },
|
||||
keepalive: {
|
||||
keepalive: {
|
||||
interval: 10000,
|
||||
idleInterval: 300000,
|
||||
forceNoop: true
|
||||
}
|
||||
forceNoop: true,
|
||||
},
|
||||
};
|
||||
|
||||
class EmailBotService {
|
||||
@@ -66,7 +66,7 @@ class EmailBotService {
|
||||
try {
|
||||
// Отправляем код на email
|
||||
await this.sendVerificationCode(email, code);
|
||||
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
logger.error('Error initializing email verification:', error);
|
||||
@@ -91,7 +91,7 @@ class EmailBotService {
|
||||
</div>
|
||||
<p style="font-size: 14px; color: #999;">Код действителен в течение 15 минут.</p>
|
||||
</div>
|
||||
`
|
||||
`,
|
||||
};
|
||||
|
||||
await this.transporter.sendMail(mailOptions);
|
||||
@@ -121,7 +121,7 @@ class EmailBotService {
|
||||
this.imap.end();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Ищем непрочитанные письма
|
||||
this.imap.search(['UNSEEN'], (err, results) => {
|
||||
if (err) {
|
||||
@@ -129,16 +129,16 @@ class EmailBotService {
|
||||
this.imap.end();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!results || results.length === 0) {
|
||||
logger.info('No new messages found');
|
||||
this.imap.end();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const f = this.imap.fetch(results, { bodies: '' });
|
||||
|
||||
|
||||
f.on('message', (msg, seqno) => {
|
||||
msg.on('body', (stream, info) => {
|
||||
simpleParser(stream, async (err, parsed) => {
|
||||
@@ -149,11 +149,11 @@ class EmailBotService {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
f.once('error', (err) => {
|
||||
logger.error(`Fetch error: ${err}`);
|
||||
});
|
||||
|
||||
|
||||
f.once('end', () => {
|
||||
try {
|
||||
this.imap.end();
|
||||
@@ -172,7 +172,7 @@ class EmailBotService {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
this.imap.connect();
|
||||
} catch (error) {
|
||||
logger.error(`Global error checking emails: ${error.message}`);
|
||||
@@ -191,7 +191,7 @@ class EmailBotService {
|
||||
from: process.env.EMAIL_USER,
|
||||
to,
|
||||
subject,
|
||||
text
|
||||
text,
|
||||
};
|
||||
|
||||
await this.transporter.sendMail(mailOptions);
|
||||
|
||||
@@ -15,19 +15,19 @@ class IdentityService {
|
||||
if (!provider || !providerId) {
|
||||
return { provider, providerId };
|
||||
}
|
||||
|
||||
|
||||
// Приводим провайдер к нижнему регистру
|
||||
const normalizedProvider = provider.toLowerCase();
|
||||
|
||||
|
||||
// Для email и wallet приводим значение к нижнему регистру
|
||||
let normalizedProviderId = providerId;
|
||||
if (normalizedProvider === 'wallet' || normalizedProvider === 'email') {
|
||||
normalizedProviderId = providerId.toLowerCase();
|
||||
}
|
||||
|
||||
return {
|
||||
provider: normalizedProvider,
|
||||
providerId: normalizedProviderId
|
||||
|
||||
return {
|
||||
provider: normalizedProvider,
|
||||
providerId: normalizedProviderId,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -42,21 +42,25 @@ class IdentityService {
|
||||
async saveIdentity(userId, provider, providerId, verified = true) {
|
||||
try {
|
||||
if (!userId || !provider || !providerId) {
|
||||
logger.warn(`[IdentityService] Missing required parameters: userId=${userId}, provider=${provider}, providerId=${providerId}`);
|
||||
logger.warn(
|
||||
`[IdentityService] Missing required parameters: userId=${userId}, provider=${provider}, providerId=${providerId}`
|
||||
);
|
||||
return {
|
||||
success: false,
|
||||
error: 'Missing required parameters'
|
||||
error: 'Missing required parameters',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Нормализуем значения
|
||||
const { provider: normalizedProvider, providerId: normalizedProviderId } =
|
||||
const { provider: normalizedProvider, providerId: normalizedProviderId } =
|
||||
this.normalizeIdentity(provider, providerId);
|
||||
|
||||
|
||||
// Проверяем тип провайдера и перенаправляем гостевые идентификаторы в guest_user_mapping
|
||||
if (normalizedProvider === 'guest') {
|
||||
logger.info(`[IdentityService] Converting guest identity for user ${userId} to guest_user_mapping: ${normalizedProviderId}`);
|
||||
|
||||
logger.info(
|
||||
`[IdentityService] Converting guest identity for user ${userId} to guest_user_mapping: ${normalizedProviderId}`
|
||||
);
|
||||
|
||||
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',
|
||||
@@ -64,41 +68,50 @@ class IdentityService {
|
||||
);
|
||||
return { success: true };
|
||||
} catch (guestError) {
|
||||
logger.error(`[IdentityService] Error saving guest identity for user ${userId}:`, guestError);
|
||||
logger.error(
|
||||
`[IdentityService] Error saving guest identity for user ${userId}:`,
|
||||
guestError
|
||||
);
|
||||
return { success: false, error: guestError.message };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Проверяем, разрешен ли такой тип провайдера
|
||||
const allowedProviders = ['email', 'wallet', 'telegram', 'username'];
|
||||
if (!allowedProviders.includes(normalizedProvider)) {
|
||||
logger.warn(`[IdentityService] Invalid provider type: ${normalizedProvider}`);
|
||||
return {
|
||||
success: false,
|
||||
error: `Invalid provider type. Allowed types: ${allowedProviders.join(', ')}`
|
||||
error: `Invalid provider type. Allowed types: ${allowedProviders.join(', ')}`,
|
||||
};
|
||||
}
|
||||
|
||||
logger.info(`[IdentityService] Saving identity for user ${userId}: ${normalizedProvider}:${normalizedProviderId}`);
|
||||
|
||||
|
||||
logger.info(
|
||||
`[IdentityService] Saving identity for user ${userId}: ${normalizedProvider}:${normalizedProviderId}`
|
||||
);
|
||||
|
||||
// Проверяем, существует ли уже такой идентификатор
|
||||
const existingResult = await db.query(
|
||||
`SELECT user_id FROM user_identities WHERE provider = $1 AND provider_id = $2`,
|
||||
[normalizedProvider, normalizedProviderId]
|
||||
);
|
||||
|
||||
|
||||
if (existingResult.rows.length > 0) {
|
||||
const existingUserId = existingResult.rows[0].user_id;
|
||||
|
||||
|
||||
// Если идентификатор уже принадлежит этому пользователю, ничего не делаем
|
||||
if (existingUserId === userId) {
|
||||
logger.info(`[IdentityService] Identity ${normalizedProvider}:${normalizedProviderId} already exists for user ${userId}`);
|
||||
logger.info(
|
||||
`[IdentityService] Identity ${normalizedProvider}:${normalizedProviderId} already exists for user ${userId}`
|
||||
);
|
||||
} else {
|
||||
// Если идентификатор принадлежит другому пользователю, логируем это
|
||||
logger.warn(`[IdentityService] Identity ${normalizedProvider}:${normalizedProviderId} already belongs to user ${existingUserId}, not user ${userId}`);
|
||||
logger.warn(
|
||||
`[IdentityService] Identity ${normalizedProvider}:${normalizedProviderId} already belongs to user ${existingUserId}, not user ${userId}`
|
||||
);
|
||||
return {
|
||||
success: false,
|
||||
error: `Identity already belongs to another user (${existingUserId})`
|
||||
error: `Identity already belongs to another user (${existingUserId})`,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
@@ -108,16 +121,21 @@ class IdentityService {
|
||||
VALUES ($1, $2, $3)`,
|
||||
[userId, normalizedProvider, normalizedProviderId]
|
||||
);
|
||||
logger.info(`[IdentityService] Created new identity ${normalizedProvider}:${normalizedProviderId} for user ${userId}`);
|
||||
logger.info(
|
||||
`[IdentityService] Created new identity ${normalizedProvider}:${normalizedProviderId} for user ${userId}`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
logger.error(`[IdentityService] Error saving identity ${provider}:${providerId} for user ${userId}:`, error);
|
||||
logger.error(
|
||||
`[IdentityService] Error saving identity ${provider}:${providerId} for user ${userId}:`,
|
||||
error
|
||||
);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Получает все идентификаторы пользователя
|
||||
* @param {number} userId - ID пользователя
|
||||
@@ -129,12 +147,12 @@ class IdentityService {
|
||||
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) {
|
||||
@@ -142,7 +160,7 @@ class IdentityService {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Получает все идентификаторы пользователя определенного типа
|
||||
* @param {number} userId - ID пользователя
|
||||
@@ -155,20 +173,25 @@ class IdentityService {
|
||||
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);
|
||||
|
||||
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);
|
||||
logger.error(
|
||||
`[IdentityService] Error getting ${provider} identities for user ${userId}:`,
|
||||
error
|
||||
);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Находит пользователя по идентификатору
|
||||
* @param {string} provider - Тип идентификатора
|
||||
@@ -178,34 +201,43 @@ class IdentityService {
|
||||
async findUserByIdentity(provider, providerId) {
|
||||
try {
|
||||
if (!provider || !providerId) {
|
||||
logger.warn(`[IdentityService] Missing parameters: provider=${provider}, providerId=${providerId}`);
|
||||
logger.warn(
|
||||
`[IdentityService] Missing parameters: provider=${provider}, providerId=${providerId}`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// Нормализуем значения
|
||||
const { provider: normalizedProvider, providerId: normalizedProviderId } =
|
||||
const { provider: normalizedProvider, providerId: normalizedProviderId } =
|
||||
this.normalizeIdentity(provider, providerId);
|
||||
|
||||
|
||||
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`,
|
||||
[normalizedProvider, normalizedProviderId]
|
||||
);
|
||||
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
logger.info(`[IdentityService] No user found with identity ${normalizedProvider}:${normalizedProviderId}`);
|
||||
logger.info(
|
||||
`[IdentityService] No user found with identity ${normalizedProvider}:${normalizedProviderId}`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
logger.info(`[IdentityService] Found user ${result.rows[0].id} with identity ${normalizedProvider}:${normalizedProviderId}`);
|
||||
|
||||
logger.info(
|
||||
`[IdentityService] Found user ${result.rows[0].id} with identity ${normalizedProvider}:${normalizedProviderId}`
|
||||
);
|
||||
return result.rows[0];
|
||||
} catch (error) {
|
||||
logger.error(`[IdentityService] Error finding user by identity ${provider}:${providerId}:`, error);
|
||||
logger.error(
|
||||
`[IdentityService] Error finding user by identity ${provider}:${providerId}:`,
|
||||
error
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Сохраняет идентификаторы из сессии для пользователя
|
||||
* @param {object} session - Объект сессии
|
||||
@@ -218,25 +250,30 @@ class IdentityService {
|
||||
logger.warn(`[IdentityService] Missing parameters: session=${!!session}, userId=${userId}`);
|
||||
return { success: false, error: 'Missing required parameters' };
|
||||
}
|
||||
|
||||
|
||||
const results = [];
|
||||
|
||||
|
||||
// Сохраняем все постоянные идентификаторы из сессии
|
||||
if (session.email) {
|
||||
const emailResult = await this.saveIdentity(userId, 'email', session.email, true);
|
||||
results.push({ type: 'email', result: emailResult });
|
||||
}
|
||||
|
||||
|
||||
if (session.address) {
|
||||
const walletResult = await this.saveIdentity(userId, 'wallet', session.address, true);
|
||||
results.push({ type: 'wallet', result: walletResult });
|
||||
}
|
||||
|
||||
|
||||
if (session.telegramId) {
|
||||
const telegramResult = await this.saveIdentity(userId, 'telegram', session.telegramId, true);
|
||||
const telegramResult = await this.saveIdentity(
|
||||
userId,
|
||||
'telegram',
|
||||
session.telegramId,
|
||||
true
|
||||
);
|
||||
results.push({ type: 'telegram', result: telegramResult });
|
||||
}
|
||||
|
||||
|
||||
// Сохраняем гостевые идентификаторы в guest_user_mapping
|
||||
if (session.guestId) {
|
||||
try {
|
||||
@@ -250,7 +287,7 @@ class IdentityService {
|
||||
results.push({ type: 'guest', result: { success: false, error: error.message } });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (session.previousGuestId && session.previousGuestId !== session.guestId) {
|
||||
try {
|
||||
await db.query(
|
||||
@@ -259,19 +296,27 @@ class IdentityService {
|
||||
);
|
||||
results.push({ type: 'previousGuest', result: { success: true } });
|
||||
} catch (error) {
|
||||
logger.error(`[IdentityService] Error saving previous guest ID for user ${userId}:`, 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}`);
|
||||
|
||||
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);
|
||||
logger.error(
|
||||
`[IdentityService] Error saving identities from session for user ${userId}:`,
|
||||
error
|
||||
);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Мигрирует все идентификаторы и сообщения от одного пользователя к другому
|
||||
* @param {number} fromUserId - ID исходного пользователя
|
||||
@@ -281,7 +326,9 @@ class IdentityService {
|
||||
async migrateUserData(fromUserId, toUserId) {
|
||||
try {
|
||||
if (!fromUserId || !toUserId) {
|
||||
logger.warn(`[IdentityService] Missing parameters: fromUserId=${fromUserId}, toUserId=${toUserId}`);
|
||||
logger.warn(
|
||||
`[IdentityService] Missing parameters: fromUserId=${fromUserId}, toUserId=${toUserId}`
|
||||
);
|
||||
return { success: false, error: 'Missing required parameters' };
|
||||
}
|
||||
|
||||
@@ -295,7 +342,7 @@ class IdentityService {
|
||||
`SELECT provider, provider_id FROM user_identities WHERE user_id = $1`,
|
||||
[fromUserId]
|
||||
);
|
||||
|
||||
|
||||
// Переносим каждый идентификатор
|
||||
for (const identity of identitiesResult.rows) {
|
||||
await client.query(
|
||||
@@ -304,7 +351,7 @@ class IdentityService {
|
||||
ON CONFLICT (provider, provider_id) DO NOTHING`,
|
||||
[toUserId, identity.provider, identity.provider_id]
|
||||
);
|
||||
|
||||
|
||||
// Удаляем старый идентификатор
|
||||
await client.query(
|
||||
`DELETE FROM user_identities
|
||||
@@ -312,13 +359,13 @@ class IdentityService {
|
||||
[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(
|
||||
@@ -329,12 +376,9 @@ class IdentityService {
|
||||
[toUserId, mapping.guest_id, mapping.processed]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Удаляем старые гостевые маппинги
|
||||
await client.query(
|
||||
`DELETE FROM guest_user_mapping WHERE user_id = $1`,
|
||||
[fromUserId]
|
||||
);
|
||||
await client.query(`DELETE FROM guest_user_mapping WHERE user_id = $1`, [fromUserId]);
|
||||
|
||||
// Переносим все сообщения
|
||||
await client.query(
|
||||
@@ -351,7 +395,7 @@ class IdentityService {
|
||||
WHERE user_id = $2`,
|
||||
[toUserId, fromUserId]
|
||||
);
|
||||
|
||||
|
||||
// Переносим настройки пользователя
|
||||
await client.query(
|
||||
`UPDATE user_preferences
|
||||
@@ -362,8 +406,10 @@ class IdentityService {
|
||||
|
||||
// Завершаем транзакцию
|
||||
await client.query('COMMIT');
|
||||
|
||||
logger.info(`[IdentityService] Successfully migrated data from user ${fromUserId} to ${toUserId}`);
|
||||
|
||||
logger.info(
|
||||
`[IdentityService] Successfully migrated data from user ${fromUserId} to ${toUserId}`
|
||||
);
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
await client.query('ROLLBACK');
|
||||
@@ -386,20 +432,20 @@ class IdentityService {
|
||||
async findRelatedUsers(identities) {
|
||||
try {
|
||||
const userIds = new Set();
|
||||
|
||||
|
||||
for (const [provider, providerId] of Object.entries(identities)) {
|
||||
if (!providerId) continue;
|
||||
|
||||
|
||||
const result = await db.query(
|
||||
`SELECT DISTINCT user_id
|
||||
FROM user_identities
|
||||
WHERE provider = $1 AND provider_id = $2`,
|
||||
[provider, providerId]
|
||||
);
|
||||
|
||||
result.rows.forEach(row => userIds.add(row.user_id));
|
||||
|
||||
result.rows.forEach((row) => userIds.add(row.user_id));
|
||||
}
|
||||
|
||||
|
||||
return Array.from(userIds);
|
||||
} catch (error) {
|
||||
logger.error(`[IdentityService] Error finding related users:`, error);
|
||||
@@ -408,4 +454,4 @@ class IdentityService {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new IdentityService();
|
||||
module.exports = new IdentityService();
|
||||
|
||||
@@ -31,5 +31,5 @@ module.exports = {
|
||||
getConversationHistory: aiAssistant.getConversationHistory,
|
||||
|
||||
telegramBot,
|
||||
aiAssistant
|
||||
aiAssistant,
|
||||
};
|
||||
|
||||
@@ -14,7 +14,7 @@ class SessionService {
|
||||
async saveSession(session) {
|
||||
try {
|
||||
return new Promise((resolve, reject) => {
|
||||
session.save(err => {
|
||||
session.save((err) => {
|
||||
if (err) {
|
||||
logger.error('Error saving session:', err);
|
||||
reject(err);
|
||||
@@ -38,7 +38,9 @@ class SessionService {
|
||||
*/
|
||||
async linkGuestMessages(session, userId) {
|
||||
try {
|
||||
logger.info(`[linkGuestMessages] Starting for user ${userId} with guestId=${session.guestId}, previousGuestId=${session.previousGuestId}`);
|
||||
logger.info(
|
||||
`[linkGuestMessages] Starting for user ${userId} with guestId=${session.guestId}, previousGuestId=${session.previousGuestId}`
|
||||
);
|
||||
|
||||
// Инициализируем массив обработанных гостевых ID, если его нет
|
||||
if (!session.processedGuestIds) {
|
||||
@@ -50,15 +52,15 @@ class SessionService {
|
||||
'SELECT guest_id FROM guest_user_mapping WHERE user_id = $1',
|
||||
[userId]
|
||||
);
|
||||
const userGuestIds = guestIdsResult.rows.map(row => row.guest_id);
|
||||
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',
|
||||
@@ -69,7 +71,7 @@ class SessionService {
|
||||
// Добавляем предыдущий гостевой 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',
|
||||
@@ -88,12 +90,11 @@ class SessionService {
|
||||
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 db.query('UPDATE guest_user_mapping SET processed = true WHERE guest_id = $1', [
|
||||
guestId,
|
||||
]);
|
||||
}
|
||||
|
||||
// Сохраняем сессию
|
||||
@@ -114,7 +115,9 @@ class SessionService {
|
||||
*/
|
||||
async processGuestMessagesWrapper(userId, guestId) {
|
||||
try {
|
||||
logger.info(`[processGuestMessagesWrapper] Processing messages: userId=${userId}, guestId=${guestId}`);
|
||||
logger.info(
|
||||
`[processGuestMessagesWrapper] Processing messages: userId=${userId}, guestId=${guestId}`
|
||||
);
|
||||
return await processGuestMessages(userId, guestId);
|
||||
} catch (error) {
|
||||
logger.error(`[processGuestMessagesWrapper] Error: ${error.message}`, error);
|
||||
@@ -146,7 +149,7 @@ class SessionService {
|
||||
async destroySession(session) {
|
||||
try {
|
||||
return new Promise((resolve, reject) => {
|
||||
session.destroy(err => {
|
||||
session.destroy((err) => {
|
||||
if (err) {
|
||||
logger.error('Error destroying session:', err);
|
||||
reject(err);
|
||||
@@ -173,29 +176,26 @@ class SessionService {
|
||||
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]
|
||||
);
|
||||
|
||||
|
||||
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 - Объект сессии
|
||||
@@ -208,23 +208,23 @@ class SessionService {
|
||||
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();
|
||||
@@ -235,16 +235,16 @@ class SessionService {
|
||||
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) {
|
||||
@@ -252,7 +252,7 @@ class SessionService {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Очищает данные аутентификации в сессии
|
||||
* @param {object} session - Объект сессии
|
||||
@@ -264,10 +264,10 @@ class SessionService {
|
||||
logger.warn('[SessionService] Cannot logout null session');
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Сохраняем гостевые ID перед очисткой
|
||||
const guestId = session.guestId;
|
||||
|
||||
|
||||
// Удаляем данные аутентификации
|
||||
delete session.userId;
|
||||
delete session.authenticated;
|
||||
@@ -278,12 +278,12 @@ class SessionService {
|
||||
delete session.telegramId;
|
||||
delete session.telegramUsername;
|
||||
delete session.telegramFirstName;
|
||||
|
||||
|
||||
// Восстанавливаем гостевой ID для продолжения работы
|
||||
if (guestId) {
|
||||
session.guestId = guestId;
|
||||
}
|
||||
|
||||
|
||||
// Сохраняем обновленную сессию
|
||||
return await this.saveSession(session, 'logout');
|
||||
} catch (error) {
|
||||
@@ -294,4 +294,4 @@ class SessionService {
|
||||
}
|
||||
|
||||
const sessionService = new SessionService();
|
||||
module.exports = sessionService;
|
||||
module.exports = sessionService;
|
||||
|
||||
@@ -20,7 +20,7 @@ async function getBot() {
|
||||
// Обработка кодов верификации
|
||||
botInstance.on('text', async (ctx) => {
|
||||
const code = ctx.message.text.trim();
|
||||
|
||||
|
||||
try {
|
||||
// Получаем код верификации для всех активных кодов с провайдером telegram
|
||||
const codeResult = await db.query(
|
||||
@@ -31,25 +31,24 @@ async function getBot() {
|
||||
AND expires_at > NOW()`,
|
||||
[code]
|
||||
);
|
||||
|
||||
|
||||
if (codeResult.rows.length === 0) {
|
||||
ctx.reply('Неверный код подтверждения');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const verification = codeResult.rows[0];
|
||||
const providerId = verification.provider_id;
|
||||
const linkedUserId = verification.user_id; // Получаем связанный userId если он есть
|
||||
const linkedUserId = verification.user_id; // Получаем связанный userId если он есть
|
||||
let userId;
|
||||
|
||||
|
||||
// Отмечаем код как использованный
|
||||
await db.query(
|
||||
'UPDATE verification_codes SET used = true WHERE id = $1',
|
||||
[verification.id]
|
||||
);
|
||||
|
||||
await db.query('UPDATE verification_codes SET used = true WHERE id = $1', [
|
||||
verification.id,
|
||||
]);
|
||||
|
||||
logger.info('Starting Telegram auth process for code:', code);
|
||||
|
||||
|
||||
// Проверяем, существует ли уже пользователь с таким Telegram ID
|
||||
const existingTelegramUser = await db.query(
|
||||
`SELECT ui.user_id
|
||||
@@ -57,7 +56,7 @@ async function getBot() {
|
||||
WHERE ui.provider = 'telegram' AND ui.provider_id = $1`,
|
||||
[ctx.from.id.toString()]
|
||||
);
|
||||
|
||||
|
||||
if (existingTelegramUser.rows.length > 0) {
|
||||
// Если пользователь с таким Telegram ID уже существует, используем его
|
||||
userId = existingTelegramUser.rows[0].user_id;
|
||||
@@ -74,7 +73,9 @@ async function getBot() {
|
||||
VALUES ($1, $2, $3, NOW())`,
|
||||
[userId, 'telegram', ctx.from.id.toString()]
|
||||
);
|
||||
logger.info(`Linked Telegram account ${ctx.from.id} to pre-authenticated user ${userId}`);
|
||||
logger.info(
|
||||
`Linked Telegram account ${ctx.from.id} to pre-authenticated user ${userId}`
|
||||
);
|
||||
} else {
|
||||
// Проверяем, есть ли пользователь, связанный с гостевым идентификатором
|
||||
let existingUserWithGuestId = null;
|
||||
@@ -85,10 +86,12 @@ async function getBot() {
|
||||
);
|
||||
if (guestUserResult.rows.length > 0) {
|
||||
existingUserWithGuestId = guestUserResult.rows[0].user_id;
|
||||
logger.info(`Found existing user ${existingUserWithGuestId} by guest ID ${providerId}`);
|
||||
logger.info(
|
||||
`Found existing user ${existingUserWithGuestId} by guest ID ${providerId}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (existingUserWithGuestId) {
|
||||
// Используем существующего пользователя и добавляем ему Telegram идентификатор
|
||||
userId = existingUserWithGuestId;
|
||||
@@ -106,7 +109,7 @@ async function getBot() {
|
||||
['user']
|
||||
);
|
||||
userId = userResult.rows[0].id;
|
||||
|
||||
|
||||
// Связываем Telegram с новым пользователем
|
||||
await db.query(
|
||||
`INSERT INTO user_identities
|
||||
@@ -114,7 +117,7 @@ async function getBot() {
|
||||
VALUES ($1, $2, $3, NOW())`,
|
||||
[userId, 'telegram', ctx.from.id.toString()]
|
||||
);
|
||||
|
||||
|
||||
// Если был гостевой ID, связываем его с новым пользователем
|
||||
if (providerId) {
|
||||
await db.query(
|
||||
@@ -125,12 +128,12 @@ async function getBot() {
|
||||
[userId, providerId]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
logger.info(`Created new user ${userId} with Telegram account ${ctx.from.id}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Обновляем сессию в базе данных
|
||||
await db.query(
|
||||
`UPDATE session
|
||||
@@ -140,23 +143,22 @@ async function getBot() {
|
||||
JSON.stringify({
|
||||
userId: userId.toString(),
|
||||
authenticated: true,
|
||||
authType: "telegram",
|
||||
telegramId: ctx.from.id.toString()
|
||||
authType: 'telegram',
|
||||
telegramId: ctx.from.id.toString(),
|
||||
}),
|
||||
JSON.stringify({guestId: providerId})
|
||||
JSON.stringify({ guestId: providerId }),
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
// Отправляем сообщение об успешной аутентификации
|
||||
await ctx.reply('Аутентификация успешна! Можете вернуться в приложение.');
|
||||
|
||||
|
||||
// Удаляем сообщение с кодом
|
||||
try {
|
||||
await ctx.deleteMessage(ctx.message.message_id);
|
||||
} catch (error) {
|
||||
logger.warn('Could not delete code message:', error);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Error in Telegram auth:', error);
|
||||
await ctx.reply('Произошла ошибка при аутентификации. Попробуйте позже.');
|
||||
@@ -166,7 +168,7 @@ async function getBot() {
|
||||
// Запускаем бота
|
||||
await botInstance.launch();
|
||||
}
|
||||
|
||||
|
||||
return botInstance;
|
||||
}
|
||||
|
||||
@@ -190,12 +192,12 @@ async function initTelegramAuth(session) {
|
||||
// Используем временный идентификатор для создания кода верификации
|
||||
// Реальный пользователь будет создан или найден при проверке кода через бота
|
||||
const tempId = crypto.randomBytes(16).toString('hex');
|
||||
|
||||
|
||||
// Если пользователь уже авторизован, сохраняем его userId в guest_user_mapping
|
||||
// чтобы потом при авторизации через бота этот пользователь был найден
|
||||
if (session && session.authenticated && session.userId) {
|
||||
const guestId = session.guestId || tempId;
|
||||
|
||||
|
||||
// Связываем гостевой ID с текущим пользователем
|
||||
await db.query(
|
||||
`INSERT INTO guest_user_mapping (user_id, guest_id)
|
||||
@@ -203,22 +205,26 @@ async function initTelegramAuth(session) {
|
||||
ON CONFLICT (guest_id) DO UPDATE SET user_id = $1`,
|
||||
[session.userId, guestId]
|
||||
);
|
||||
|
||||
logger.info(`[initTelegramAuth] Linked guestId ${guestId} to authenticated user ${session.userId}`);
|
||||
|
||||
logger.info(
|
||||
`[initTelegramAuth] Linked guestId ${guestId} to authenticated user ${session.userId}`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Создаем код через сервис верификации с идентификатором
|
||||
const code = await verificationService.createVerificationCode(
|
||||
'telegram',
|
||||
session.guestId || tempId,
|
||||
session.authenticated ? session.userId : null
|
||||
);
|
||||
|
||||
logger.info(`[initTelegramAuth] Created verification code for guestId: ${session.guestId || tempId}${session.authenticated ? `, userId: ${session.userId}` : ''}`);
|
||||
|
||||
|
||||
logger.info(
|
||||
`[initTelegramAuth] Created verification code for guestId: ${session.guestId || tempId}${session.authenticated ? `, userId: ${session.userId}` : ''}`
|
||||
);
|
||||
|
||||
return {
|
||||
verificationCode: code,
|
||||
botLink: `https://t.me/${process.env.TELEGRAM_BOT_USERNAME}`
|
||||
botLink: `https://t.me/${process.env.TELEGRAM_BOT_USERNAME}`,
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('Error initializing Telegram auth:', error);
|
||||
@@ -229,5 +235,5 @@ async function initTelegramAuth(session) {
|
||||
module.exports = {
|
||||
getBot,
|
||||
stopBot,
|
||||
initTelegramAuth
|
||||
};
|
||||
initTelegramAuth,
|
||||
};
|
||||
|
||||
@@ -9,7 +9,10 @@ class VerificationService {
|
||||
|
||||
// Генерация кода
|
||||
generateCode() {
|
||||
const code = Math.random().toString(36).substring(2, 2 + this.codeLength).toUpperCase();
|
||||
const code = Math.random()
|
||||
.toString(36)
|
||||
.substring(2, 2 + this.codeLength)
|
||||
.toUpperCase();
|
||||
logger.info(`Generated verification code: ${code}`);
|
||||
return code;
|
||||
}
|
||||
@@ -20,8 +23,10 @@ class VerificationService {
|
||||
const expiresAt = new Date(Date.now() + this.expirationMinutes * 60 * 1000);
|
||||
|
||||
try {
|
||||
logger.info(`Creating verification code for ${provider}:${providerId}, userId: ${userId || 'null'}`);
|
||||
|
||||
logger.info(
|
||||
`Creating verification code for ${provider}:${providerId}, userId: ${userId || 'null'}`
|
||||
);
|
||||
|
||||
// Если userId не указан, добавляем запись без ссылки на пользователя
|
||||
if (userId === null || userId === undefined) {
|
||||
await db.query(
|
||||
@@ -46,7 +51,7 @@ class VerificationService {
|
||||
error: error.message,
|
||||
provider,
|
||||
providerId,
|
||||
userId
|
||||
userId,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
@@ -56,11 +61,11 @@ class VerificationService {
|
||||
async verifyCode(code, provider, providerId) {
|
||||
try {
|
||||
logger.info(`Verifying code for ${provider}:${providerId}`);
|
||||
|
||||
|
||||
// Преобразуем код в верхний регистр для сравнения
|
||||
const normalizedCode = code.toUpperCase();
|
||||
logger.info(`Normalized code: ${normalizedCode}`);
|
||||
|
||||
|
||||
// Проверим, есть ли такой код в базе (для отладки)
|
||||
const checkResult = await db.query(
|
||||
`SELECT code FROM verification_codes
|
||||
@@ -70,13 +75,15 @@ class VerificationService {
|
||||
AND expires_at > NOW()`,
|
||||
[provider, providerId]
|
||||
);
|
||||
|
||||
|
||||
if (checkResult.rows.length > 0) {
|
||||
logger.info(`Found codes for ${provider}:${providerId}: ${JSON.stringify(checkResult.rows.map(r => r.code))}`);
|
||||
logger.info(
|
||||
`Found codes for ${provider}:${providerId}: ${JSON.stringify(checkResult.rows.map((r) => r.code))}`
|
||||
);
|
||||
} else {
|
||||
logger.warn(`No active codes found for ${provider}:${providerId}`);
|
||||
}
|
||||
|
||||
|
||||
const result = await db.query(
|
||||
`SELECT * FROM verification_codes
|
||||
WHERE code = $1
|
||||
@@ -88,30 +95,29 @@ class VerificationService {
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
logger.warn(`Invalid or expired code for ${provider}:${providerId}. Input: ${normalizedCode}`);
|
||||
logger.warn(
|
||||
`Invalid or expired code for ${provider}:${providerId}. Input: ${normalizedCode}`
|
||||
);
|
||||
return { success: false, error: 'Неверный или истекший код' };
|
||||
}
|
||||
|
||||
const verification = result.rows[0];
|
||||
|
||||
// Отмечаем код как использованный
|
||||
await db.query(
|
||||
'UPDATE verification_codes SET used = true WHERE id = $1',
|
||||
[verification.id]
|
||||
);
|
||||
await db.query('UPDATE verification_codes SET used = true WHERE id = $1', [verification.id]);
|
||||
|
||||
logger.info(`Code verified successfully for ${provider}:${providerId}`);
|
||||
return {
|
||||
success: true,
|
||||
userId: verification.user_id,
|
||||
providerId: verification.provider_id
|
||||
providerId: verification.provider_id,
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('Error verifying code:', {
|
||||
error: error.message,
|
||||
code,
|
||||
provider,
|
||||
providerId
|
||||
providerId,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
@@ -131,4 +137,4 @@ class VerificationService {
|
||||
}
|
||||
|
||||
const verificationService = new VerificationService();
|
||||
module.exports = verificationService;
|
||||
module.exports = verificationService;
|
||||
|
||||
Reference in New Issue
Block a user