ваше сообщение коммита
This commit is contained in:
@@ -30,8 +30,15 @@ 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) === ethers.getAddress(address);
|
||||
|
||||
// Сравниваем нормализованные адреса
|
||||
return ethers.getAddress(recoveredAddress).toLowerCase() === normalizedAddress;
|
||||
} catch (error) {
|
||||
logger.error('Error in signature verification:', error);
|
||||
return false;
|
||||
@@ -45,15 +52,15 @@ class AuthService {
|
||||
*/
|
||||
async findOrCreateUser(address) {
|
||||
try {
|
||||
// Нормализуем адрес
|
||||
address = ethers.getAddress(address);
|
||||
// Нормализуем адрес - всегда приводим к нижнему регистру
|
||||
const normalizedAddress = ethers.getAddress(address).toLowerCase();
|
||||
|
||||
// Ищем пользователя по адресу в таблице user_identities
|
||||
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
|
||||
`, [address]);
|
||||
`, [normalizedAddress]);
|
||||
|
||||
if (userResult.rows.length > 0) {
|
||||
const user = userResult.rows[0];
|
||||
@@ -71,13 +78,22 @@ class AuthService {
|
||||
|
||||
const userId = newUserResult.rows[0].id;
|
||||
|
||||
// Добавляем идентификатор кошелька
|
||||
// Добавляем идентификатор кошелька (всегда в нижнем регистре)
|
||||
await db.query(
|
||||
'INSERT INTO user_identities (user_id, provider, provider_id) VALUES ($1, $2, $3)',
|
||||
[userId, 'wallet', address]
|
||||
[userId, 'wallet', normalizedAddress]
|
||||
);
|
||||
|
||||
return { userId, isAdmin: false };
|
||||
// Проверяем, есть ли у пользователя роль админа
|
||||
const isAdmin = await this.checkAdminRole(normalizedAddress);
|
||||
|
||||
// Если у пользователя есть админские токены, обновляем его роль
|
||||
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`);
|
||||
}
|
||||
|
||||
return { userId, isAdmin };
|
||||
} catch (error) {
|
||||
console.error('Error finding or creating user:', error);
|
||||
throw error;
|
||||
@@ -454,8 +470,8 @@ class AuthService {
|
||||
// Если есть гостевой ID в сессии, сохраняем его для нового пользователя
|
||||
if (session.guestId && isNewUser) {
|
||||
await db.query(
|
||||
'INSERT INTO user_identities (user_id, provider, provider_id) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING',
|
||||
[userId, 'guest', session.guestId]
|
||||
'INSERT INTO guest_user_mapping (user_id, guest_id) VALUES ($1, $2) ON CONFLICT (guest_id) DO UPDATE SET user_id = $1',
|
||||
[userId, session.guestId]
|
||||
);
|
||||
logger.info(`[verifyTelegramAuth] Saved guest ID ${session.guestId} for user ${userId}`);
|
||||
}
|
||||
@@ -597,18 +613,25 @@ class AuthService {
|
||||
}
|
||||
|
||||
// Нормализуем значение идентификатора
|
||||
if (provider === 'wallet' && providerId) {
|
||||
providerId = providerId.toLowerCase();
|
||||
} else if (provider === 'email' && providerId) {
|
||||
providerId = providerId.toLowerCase();
|
||||
let normalizedProviderId = providerId;
|
||||
if (provider === 'wallet') {
|
||||
// Для кошельков используем ethers для валидации и нормализации
|
||||
try {
|
||||
normalizedProviderId = ethers.getAddress(providerId).toLowerCase();
|
||||
} catch (error) {
|
||||
logger.error(`[AuthService] Invalid wallet address: ${providerId}`, error);
|
||||
throw new Error('Invalid wallet address');
|
||||
}
|
||||
} else if (provider === 'email') {
|
||||
normalizedProviderId = providerId.toLowerCase();
|
||||
}
|
||||
|
||||
logger.info(`[AuthService] Linking identity ${provider}:${providerId} 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, providerId]
|
||||
[provider, normalizedProviderId]
|
||||
);
|
||||
|
||||
if (existingResult.rows.length > 0) {
|
||||
@@ -616,11 +639,11 @@ class AuthService {
|
||||
|
||||
// Если идентификатор уже принадлежит этому пользователю, ничего не делаем
|
||||
if (existingUserId === userId) {
|
||||
logger.info(`[AuthService] Identity ${provider}:${providerId} 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}:${providerId} 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})`);
|
||||
}
|
||||
}
|
||||
@@ -629,13 +652,13 @@ class AuthService {
|
||||
await db.query(
|
||||
`INSERT INTO user_identities (user_id, provider, provider_id)
|
||||
VALUES ($1, $2, $3)`,
|
||||
[userId, provider, providerId]
|
||||
[userId, provider, normalizedProviderId]
|
||||
);
|
||||
|
||||
// Проверяем и обновляем роль администратора, если это идентификатор кошелька
|
||||
let isAdmin = false;
|
||||
if (provider === 'wallet') {
|
||||
isAdmin = await this.checkAdminTokens(providerId);
|
||||
isAdmin = await this.checkAdminTokens(normalizedProviderId);
|
||||
|
||||
// Обновляем роль пользователя в базе данных, если нужно
|
||||
if (isAdmin) {
|
||||
@@ -647,7 +670,7 @@ class AuthService {
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(`[AuthService] Identity ${provider}:${providerId} 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);
|
||||
|
||||
@@ -5,6 +5,32 @@ const logger = require('../utils/logger');
|
||||
* Сервис для работы с идентификаторами пользователей
|
||||
*/
|
||||
class IdentityService {
|
||||
/**
|
||||
* Нормализует значения идентификаторов (приводит к нижнему регистру где нужно)
|
||||
* @param {string} provider - Тип идентификатора
|
||||
* @param {string} providerId - Значение идентификатора
|
||||
* @returns {object} - Нормализованные значения
|
||||
*/
|
||||
normalizeIdentity(provider, providerId) {
|
||||
if (!provider || !providerId) {
|
||||
return { provider, providerId };
|
||||
}
|
||||
|
||||
// Приводим провайдер к нижнему регистру
|
||||
const normalizedProvider = provider.toLowerCase();
|
||||
|
||||
// Для email и wallet приводим значение к нижнему регистру
|
||||
let normalizedProviderId = providerId;
|
||||
if (normalizedProvider === 'wallet' || normalizedProvider === 'email') {
|
||||
normalizedProviderId = providerId.toLowerCase();
|
||||
}
|
||||
|
||||
return {
|
||||
provider: normalizedProvider,
|
||||
providerId: normalizedProviderId
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохраняет идентификатор пользователя в базу данных
|
||||
* @param {number} userId - ID пользователя
|
||||
@@ -23,20 +49,18 @@ class IdentityService {
|
||||
};
|
||||
}
|
||||
|
||||
// Приводим provider и providerId к нужному формату
|
||||
provider = provider.toLowerCase();
|
||||
if (provider === 'wallet' || provider === 'email') {
|
||||
providerId = providerId.toLowerCase();
|
||||
}
|
||||
// Нормализуем значения
|
||||
const { provider: normalizedProvider, providerId: normalizedProviderId } =
|
||||
this.normalizeIdentity(provider, providerId);
|
||||
|
||||
// Проверяем тип провайдера и перенаправляем гостевые идентификаторы в guest_user_mapping
|
||||
if (provider === 'guest') {
|
||||
logger.info(`[IdentityService] Converting guest identity for user ${userId} to guest_user_mapping: ${providerId}`);
|
||||
if (normalizedProvider === 'guest') {
|
||||
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',
|
||||
[userId, providerId]
|
||||
[userId, normalizedProviderId]
|
||||
);
|
||||
return { success: true };
|
||||
} catch (guestError) {
|
||||
@@ -47,20 +71,20 @@ class IdentityService {
|
||||
|
||||
// Проверяем, разрешен ли такой тип провайдера
|
||||
const allowedProviders = ['email', 'wallet', 'telegram', 'username'];
|
||||
if (!allowedProviders.includes(provider)) {
|
||||
logger.warn(`[IdentityService] Invalid provider type: ${provider}`);
|
||||
if (!allowedProviders.includes(normalizedProvider)) {
|
||||
logger.warn(`[IdentityService] Invalid provider type: ${normalizedProvider}`);
|
||||
return {
|
||||
success: false,
|
||||
error: `Invalid provider type. Allowed types: ${allowedProviders.join(', ')}`
|
||||
};
|
||||
}
|
||||
|
||||
logger.info(`[IdentityService] Saving identity for user ${userId}: ${provider}:${providerId}`);
|
||||
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`,
|
||||
[provider, providerId]
|
||||
[normalizedProvider, normalizedProviderId]
|
||||
);
|
||||
|
||||
if (existingResult.rows.length > 0) {
|
||||
@@ -68,10 +92,10 @@ class IdentityService {
|
||||
|
||||
// Если идентификатор уже принадлежит этому пользователю, ничего не делаем
|
||||
if (existingUserId === userId) {
|
||||
logger.info(`[IdentityService] Identity ${provider}:${providerId} already exists for user ${userId}`);
|
||||
logger.info(`[IdentityService] Identity ${normalizedProvider}:${normalizedProviderId} already exists for user ${userId}`);
|
||||
} else {
|
||||
// Если идентификатор принадлежит другому пользователю, логируем это
|
||||
logger.warn(`[IdentityService] Identity ${provider}:${providerId} 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})`
|
||||
@@ -82,9 +106,9 @@ class IdentityService {
|
||||
await db.query(
|
||||
`INSERT INTO user_identities (user_id, provider, provider_id)
|
||||
VALUES ($1, $2, $3)`,
|
||||
[userId, provider, providerId]
|
||||
[userId, normalizedProvider, normalizedProviderId]
|
||||
);
|
||||
logger.info(`[IdentityService] Created new identity ${provider}:${providerId} for user ${userId}`);
|
||||
logger.info(`[IdentityService] Created new identity ${normalizedProvider}:${normalizedProviderId} for user ${userId}`);
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
@@ -158,19 +182,23 @@ class IdentityService {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Нормализуем значения
|
||||
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`,
|
||||
[provider, providerId]
|
||||
[normalizedProvider, normalizedProviderId]
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
logger.info(`[IdentityService] No user found with identity ${provider}:${providerId}`);
|
||||
logger.info(`[IdentityService] No user found with identity ${normalizedProvider}:${normalizedProviderId}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
logger.info(`[IdentityService] Found user ${result.rows[0].id} with identity ${provider}:${providerId}`);
|
||||
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);
|
||||
@@ -195,12 +223,12 @@ class IdentityService {
|
||||
|
||||
// Сохраняем все постоянные идентификаторы из сессии
|
||||
if (session.email) {
|
||||
const emailResult = await this.saveIdentity(userId, 'email', session.email.toLowerCase(), true);
|
||||
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.toLowerCase(), true);
|
||||
const walletResult = await this.saveIdentity(userId, 'wallet', session.address, true);
|
||||
results.push({ type: 'wallet', result: walletResult });
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ async function getBot() {
|
||||
|
||||
const verification = codeResult.rows[0];
|
||||
const providerId = verification.provider_id;
|
||||
const linkedUserId = verification.user_id; // Получаем связанный userId если он есть
|
||||
let userId;
|
||||
|
||||
// Отмечаем код как использованный
|
||||
@@ -62,33 +63,72 @@ async function getBot() {
|
||||
userId = existingTelegramUser.rows[0].user_id;
|
||||
logger.info(`Using existing user ${userId} for Telegram account ${ctx.from.id}`);
|
||||
} else {
|
||||
// Создаем нового пользователя, если нет существующего с этим Telegram ID
|
||||
const userResult = await db.query(
|
||||
'INSERT INTO users (created_at, role) VALUES (NOW(), $1) RETURNING id',
|
||||
['user']
|
||||
);
|
||||
userId = userResult.rows[0].id;
|
||||
|
||||
// Связываем Telegram с новым пользователем
|
||||
await db.query(
|
||||
`INSERT INTO user_identities
|
||||
(user_id, provider, provider_id, created_at)
|
||||
VALUES ($1, $2, $3, NOW())`,
|
||||
[userId, 'telegram', ctx.from.id.toString()]
|
||||
);
|
||||
|
||||
// Если был гостевой ID, связываем его с новым пользователем
|
||||
if (providerId) {
|
||||
// Если код верификации был связан с существующим пользователем, используем его
|
||||
if (linkedUserId) {
|
||||
// Используем userId из кода верификации
|
||||
userId = linkedUserId;
|
||||
// Связываем Telegram с этим пользователем
|
||||
await db.query(
|
||||
`INSERT INTO user_identities
|
||||
(user_id, provider, provider_id, created_at)
|
||||
VALUES ($1, $2, $3, NOW())
|
||||
ON CONFLICT (provider, provider_id) DO NOTHING`,
|
||||
[userId, 'guest', providerId]
|
||||
VALUES ($1, $2, $3, NOW())`,
|
||||
[userId, 'telegram', ctx.from.id.toString()]
|
||||
);
|
||||
logger.info(`Linked Telegram account ${ctx.from.id} to pre-authenticated user ${userId}`);
|
||||
} else {
|
||||
// Проверяем, есть ли пользователь, связанный с гостевым идентификатором
|
||||
let existingUserWithGuestId = null;
|
||||
if (providerId) {
|
||||
const guestUserResult = await db.query(
|
||||
`SELECT user_id FROM guest_user_mapping WHERE guest_id = $1`,
|
||||
[providerId]
|
||||
);
|
||||
if (guestUserResult.rows.length > 0) {
|
||||
existingUserWithGuestId = guestUserResult.rows[0].user_id;
|
||||
logger.info(`Found existing user ${existingUserWithGuestId} by guest ID ${providerId}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (existingUserWithGuestId) {
|
||||
// Используем существующего пользователя и добавляем ему Telegram идентификатор
|
||||
userId = existingUserWithGuestId;
|
||||
await db.query(
|
||||
`INSERT INTO user_identities
|
||||
(user_id, provider, provider_id, created_at)
|
||||
VALUES ($1, $2, $3, NOW())`,
|
||||
[userId, 'telegram', ctx.from.id.toString()]
|
||||
);
|
||||
logger.info(`Linked Telegram account ${ctx.from.id} to existing user ${userId}`);
|
||||
} else {
|
||||
// Создаем нового пользователя, если не нашли существующего
|
||||
const userResult = await db.query(
|
||||
'INSERT INTO users (created_at, role) VALUES (NOW(), $1) RETURNING id',
|
||||
['user']
|
||||
);
|
||||
userId = userResult.rows[0].id;
|
||||
|
||||
// Связываем Telegram с новым пользователем
|
||||
await db.query(
|
||||
`INSERT INTO user_identities
|
||||
(user_id, provider, provider_id, created_at)
|
||||
VALUES ($1, $2, $3, NOW())`,
|
||||
[userId, 'telegram', ctx.from.id.toString()]
|
||||
);
|
||||
|
||||
// Если был гостевой ID, связываем его с новым пользователем
|
||||
if (providerId) {
|
||||
await db.query(
|
||||
`INSERT INTO guest_user_mapping
|
||||
(user_id, guest_id)
|
||||
VALUES ($1, $2)
|
||||
ON CONFLICT (guest_id) DO UPDATE SET user_id = $1`,
|
||||
[userId, providerId]
|
||||
);
|
||||
}
|
||||
|
||||
logger.info(`Created new user ${userId} with Telegram account ${ctx.from.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(`Created new user ${userId} with Telegram account ${ctx.from.id}`);
|
||||
}
|
||||
|
||||
// Обновляем сессию в базе данных
|
||||
@@ -151,14 +191,30 @@ 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)
|
||||
VALUES ($1, $2)
|
||||
ON CONFLICT (guest_id) DO UPDATE SET user_id = $1`,
|
||||
[session.userId, guestId]
|
||||
);
|
||||
|
||||
logger.info(`[initTelegramAuth] Linked guestId ${guestId} to authenticated user ${session.userId}`);
|
||||
}
|
||||
|
||||
// Создаем код через сервис верификации с идентификатором
|
||||
const code = await verificationService.createVerificationCode(
|
||||
'telegram',
|
||||
session.guestId || tempId,
|
||||
null // Не привязываем к конкретному userId на этом этапе
|
||||
session.authenticated ? session.userId : null
|
||||
);
|
||||
|
||||
logger.info(`[initTelegramAuth] Created verification code for guestId: ${session.guestId || tempId}`);
|
||||
logger.info(`[initTelegramAuth] Created verification code for guestId: ${session.guestId || tempId}${session.authenticated ? `, userId: ${session.userId}` : ''}`);
|
||||
|
||||
return {
|
||||
verificationCode: code,
|
||||
|
||||
Reference in New Issue
Block a user