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

This commit is contained in:
2025-04-16 16:39:58 +03:00
parent 31189163af
commit f371521511
11 changed files with 777 additions and 76 deletions

View File

@@ -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);

View File

@@ -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 });
}

View File

@@ -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,