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

This commit is contained in:
2025-04-21 18:03:43 +03:00
parent 9482443e2d
commit d47bba1d1b
39 changed files with 7814 additions and 1425 deletions

View File

@@ -35,29 +35,28 @@ router.get('/nonce', async (req, res) => {
// Генерируем случайный nonce
const nonce = crypto.randomBytes(16).toString('hex');
// Проверяем, существует ли уже nonce для этого адреса
const existingNonce = await db.query(
'SELECT id FROM nonces WHERE identity_value = $1',
[address.toLowerCase()]
);
const existingNonce = await db.query('SELECT id FROM nonces WHERE identity_value = $1', [
address.toLowerCase(),
]);
if (existingNonce.rows.length > 0) {
// Обновляем существующий nonce
await db.query(
'UPDATE nonces SET nonce = $1, expires_at = NOW() + INTERVAL \'15 minutes\' WHERE identity_value = $2',
"UPDATE nonces SET nonce = $1, expires_at = NOW() + INTERVAL '15 minutes' WHERE identity_value = $2",
[nonce, address.toLowerCase()]
);
} else {
// Создаем новый nonce
await db.query(
'INSERT INTO nonces (identity_value, nonce, expires_at) VALUES ($1, $2, NOW() + INTERVAL \'15 minutes\')',
"INSERT INTO nonces (identity_value, nonce, expires_at) VALUES ($1, $2, NOW() + INTERVAL '15 minutes')",
[address.toLowerCase(), nonce]
);
}
logger.info(`Nonce ${nonce} сохранен для адреса ${address}`);
res.json({ nonce });
} catch (error) {
logger.error('Error generating nonce:', error);
@@ -69,53 +68,58 @@ router.get('/nonce', async (req, res) => {
router.post('/verify', async (req, res) => {
try {
const { address, message, signature } = req.body;
logger.info(`[verify] Verifying signature for address: ${address}`);
// Сохраняем гостевые ID до проверки
const guestId = req.session.guestId;
const previousGuestId = req.session.previousGuestId;
// Проверяем подпись
const isValid = await authService.verifySignature(message, signature, address);
if (!isValid) {
return res.status(401).json({ success: false, error: 'Invalid signature' });
}
// Нормализуем адрес для использования в запросах
const normalizedAddress = ethers.getAddress(address).toLowerCase();
// Проверяем nonce
const nonceResult = await db.query('SELECT nonce FROM nonces WHERE identity_value = $1', [normalizedAddress]);
if (nonceResult.rows.length === 0 || nonceResult.rows[0].nonce !== message.match(/Nonce: ([^\n]+)/)[1]) {
const nonceResult = await db.query('SELECT nonce FROM nonces WHERE identity_value = $1', [
normalizedAddress,
]);
if (
nonceResult.rows.length === 0 ||
nonceResult.rows[0].nonce !== message.match(/Nonce: ([^\n]+)/)[1]
) {
return res.status(401).json({ success: false, error: 'Invalid nonce' });
}
let userId;
let isAdmin = false;
// Проверяем, авторизован ли пользователь уже
if (req.session.authenticated && req.session.userId) {
// Если пользователь уже авторизован, привязываем кошелек к существующему пользователю
userId = req.session.userId;
logger.info(`[verify] Using existing authenticated user ${userId} for wallet ${normalizedAddress}`);
// Связываем кошелек с пользователем через identity-service для предотвращения дубликатов
const linkResult = await authService.linkIdentity(
userId,
'wallet',
address
logger.info(
`[verify] Using existing authenticated user ${userId} for wallet ${normalizedAddress}`
);
// Связываем кошелек с пользователем через identity-service для предотвращения дубликатов
const linkResult = await authService.linkIdentity(userId, 'wallet', address);
if (!linkResult.success && linkResult.error) {
return res.status(400).json({
success: false,
error: linkResult.error
error: linkResult.error,
});
}
// Если linkResult.message содержит 'already exists', значит кошелек уже привязан
logger.info(`[verify] Wallet ${normalizedAddress} linked to user ${userId}: ${linkResult.message || 'success'}`);
logger.info(
`[verify] Wallet ${normalizedAddress} linked to user ${userId}: ${linkResult.message || 'success'}`
);
} else {
// Находим или создаем пользователя, если не авторизован
const result = await authService.findOrCreateUser(address);
@@ -123,49 +127,48 @@ router.post('/verify', async (req, res) => {
isAdmin = result.isAdmin;
logger.info(`[verify] Found or created user ${userId} for wallet ${normalizedAddress}`);
}
// Сохраняем идентификаторы гостевой сессии
if (guestId) {
await identityService.saveIdentity(userId, 'guest', guestId, true);
}
if (previousGuestId && previousGuestId !== guestId) {
await identityService.saveIdentity(userId, 'guest', previousGuestId, true);
}
// Проверяем наличие админских токенов
const adminStatus = await authService.checkAdminTokens(normalizedAddress);
if (adminStatus) {
await db.query('UPDATE users SET role = $1 WHERE id = $2', ['admin', userId]);
isAdmin = true;
}
// Обновляем сессию
req.session.userId = userId;
req.session.authenticated = true;
req.session.authType = 'wallet';
req.session.isAdmin = adminStatus || isAdmin;
req.session.address = normalizedAddress; // Всегда сохраняем нормализованный адрес
// Удаляем временный ID
delete req.session.tempUserId;
// Сохраняем сессию
await sessionService.saveSession(req.session);
// Связываем гостевые сообщения с пользователем
await sessionService.linkGuestMessages(req.session, userId);
// Возвращаем успешный ответ
return res.json({
success: true,
userId,
address: normalizedAddress, // Возвращаем нормализованный адрес
isAdmin: adminStatus || isAdmin,
authenticated: true
authenticated: true,
});
} catch (error) {
logger.error('[verify] Error:', error);
res.status(500).json({ success: false, error: 'Server error' });
@@ -176,17 +179,17 @@ router.post('/verify', async (req, res) => {
router.post('/telegram/verify', async (req, res) => {
try {
const { telegramId, verificationCode } = req.body;
if (!telegramId || !verificationCode) {
return res.status(400).json({
success: false,
error: 'Missing required fields'
error: 'Missing required fields',
});
}
// Сохраняем гостевой ID из текущей сессии
const guestId = req.session.guestId;
// Передаем сессию в метод верификации
const verificationResult = await authService.verifyTelegramAuth(
telegramId,
@@ -197,7 +200,7 @@ router.post('/telegram/verify', async (req, res) => {
if (!verificationResult.success) {
return res.status(400).json({
success: false,
error: verificationResult.error || 'Verification failed'
error: verificationResult.error || 'Verification failed',
});
}
@@ -205,9 +208,9 @@ router.post('/telegram/verify', async (req, res) => {
req.session.regenerate(async (err) => {
if (err) {
logger.error('[telegram/verify] Error regenerating session:', err);
return res.status(500).json({
success: false,
error: 'Session error'
return res.status(500).json({
success: false,
error: 'Session error',
});
}
@@ -217,7 +220,7 @@ router.post('/telegram/verify', async (req, res) => {
req.session.authType = 'telegram';
req.session.authenticated = true;
req.session.role = verificationResult.role;
// Восстанавливаем гостевой ID, если он был
if (guestId) {
req.session.guestId = guestId;
@@ -236,14 +239,14 @@ router.post('/telegram/verify', async (req, res) => {
userId: verificationResult.userId,
role: verificationResult.role,
telegramId,
isNewUser: verificationResult.isNewUser
isNewUser: verificationResult.isNewUser,
});
});
} catch (error) {
logger.error('[telegram/verify] Error:', error);
return res.status(500).json({
success: false,
error: 'Internal server error'
error: 'Internal server error',
});
}
});
@@ -252,26 +255,26 @@ router.post('/telegram/verify', async (req, res) => {
router.post('/email/request', authLimiter, async (req, res) => {
try {
const { email } = req.body;
if (!email || !email.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) {
return res.status(400).json({ error: 'Invalid email format' });
}
// Инициализация email аутентификации
const result = await emailAuth.initEmailAuth(req.session, email);
// Сохраняем сессию после установки pendingEmail
await sessionService.saveSession(req.session);
if (result.success) {
res.json({
success: true,
message: 'Код подтверждения отправлен на email'
res.json({
success: true,
message: 'Код подтверждения отправлен на email',
});
} else {
res.status(500).json({
success: false,
error: result.error || 'Ошибка отправки кода'
res.status(500).json({
success: false,
error: result.error || 'Ошибка отправки кода',
});
}
} catch (error) {
@@ -284,150 +287,155 @@ router.post('/email/request', authLimiter, async (req, res) => {
router.post('/email/verify-code', async (req, res) => {
try {
const { code, email } = req.body;
if (!code) {
return res.status(400).json({
success: false,
error: 'Код подтверждения обязателен'
return res.status(400).json({
success: false,
error: 'Код подтверждения обязателен',
});
}
// Если email передан в запросе, сохраняем его в сессии
if (email && !req.session.pendingEmail) {
req.session.pendingEmail = email.toLowerCase();
}
if (!req.session.pendingEmail) {
return res.status(400).json({
success: false,
error: 'Email не найден в сессии. Пожалуйста, запросите код подтверждения снова.'
error: 'Email не найден в сессии. Пожалуйста, запросите код подтверждения снова.',
});
}
// Сохраняем гостевой ID до проверки
const guestId = req.session.guestId;
const previousGuestId = req.session.previousGuestId;
// Проверяем код через сервис верификации
const verificationResult = await verificationService.verifyCode(
code,
'email',
code,
'email',
req.session.pendingEmail
);
if (!verificationResult.success) {
return res.status(400).json({
success: false,
error: verificationResult.error || 'Неверный код подтверждения'
error: verificationResult.error || 'Неверный код подтверждения',
});
}
// Получаем или создаем пользователя
let userId;
let isNewAuth = false;
// Проверяем, авторизован ли пользователь
if (req.session.authenticated && req.session.userId) {
// Связываем email с существующим пользователем
userId = req.session.userId;
logger.info(`[email/verify-code] Linking email ${req.session.pendingEmail} to existing authenticated user ${userId}`);
// Связываем email с текущим аккаунтом
const linkResult = await authService.linkIdentity(
userId,
'email',
req.session.pendingEmail
logger.info(
`[email/verify-code] Linking email ${req.session.pendingEmail} to existing authenticated user ${userId}`
);
// Связываем email с текущим аккаунтом
const linkResult = await authService.linkIdentity(userId, 'email', req.session.pendingEmail);
// Сохраняем email в сессии
req.session.email = req.session.pendingEmail;
// Удаляем временные данные
delete req.session.pendingEmail;
// Сохраняем сессию
await sessionService.saveSession(req.session);
return res.json({
return res.json({
success: true,
userId,
email: req.session.email,
authenticated: true,
linked: true
linked: true,
});
} else {
// Если пользователь не авторизован, ищем существующего пользователя или создаем нового
// Ищем существующего пользователя по email
const existingUser = await identityService.findUserByIdentity(
'email',
'email',
req.session.pendingEmail
);
if (existingUser) {
// Используем существующего пользователя
userId = existingUser.id;
logger.info(`[email/verify-code] Using existing user ${userId} with email ${req.session.pendingEmail}`);
logger.info(
`[email/verify-code] Using existing user ${userId} with email ${req.session.pendingEmail}`
);
} else if (req.session.userId) {
// Используем текущего пользователя
userId = req.session.userId;
logger.info(`[email/verify-code] Using current user ${userId} for email ${req.session.pendingEmail}`);
logger.info(
`[email/verify-code] Using current user ${userId} for email ${req.session.pendingEmail}`
);
} else if (req.session.tempUserId) {
// Используем временного пользователя
userId = req.session.tempUserId;
logger.info(`[email/verify-code] Using temporary user ${userId} for email ${req.session.pendingEmail}`);
logger.info(
`[email/verify-code] Using temporary user ${userId} for email ${req.session.pendingEmail}`
);
} else {
// Создаем нового пользователя
const newUser = await db.query(
'INSERT INTO users (role) VALUES ($1) RETURNING id',
['user']
);
const newUser = await db.query('INSERT INTO users (role) VALUES ($1) RETURNING id', [
'user',
]);
userId = newUser.rows[0].id;
isNewAuth = true;
logger.info(`[email/verify-code] Created new user ${userId} for email ${req.session.pendingEmail}`);
logger.info(
`[email/verify-code] Created new user ${userId} for email ${req.session.pendingEmail}`
);
}
// Сохраняем email как идентификатор
await identityService.saveIdentity(userId, 'email', req.session.pendingEmail, true);
// Сохраняем гостевые идентификаторы
if (guestId) {
await identityService.saveIdentity(userId, 'guest', guestId, true);
}
if (previousGuestId && previousGuestId !== guestId) {
await identityService.saveIdentity(userId, 'guest', previousGuestId, true);
}
// Устанавливаем сессию
req.session.userId = userId;
req.session.authenticated = true;
req.session.authType = 'email';
req.session.email = req.session.pendingEmail;
// Удаляем временные данные
delete req.session.tempUserId;
delete req.session.pendingEmail;
// Сохраняем сессию
await sessionService.saveSession(req.session);
// Связываем гостевые сообщения
await sessionService.linkGuestMessages(req.session, userId);
return res.json({
return res.json({
success: true,
userId,
email: req.session.email,
authenticated: true,
isNewAuth
isNewAuth,
});
}
} catch (error) {
logger.error('[email/verify-code] Error:', error);
return res.status(500).json({
success: false,
error: 'Ошибка сервера'
return res.status(500).json({
success: false,
error: 'Ошибка сервера',
});
}
});
@@ -436,29 +444,29 @@ router.post('/email/verify-code', async (req, res) => {
router.post('/telegram/init', async (req, res) => {
try {
const { verificationCode, botLink } = await initTelegramAuth(req.session);
if (!verificationCode || !botLink) {
throw new Error('Failed to generate verification code');
}
res.json({
res.json({
success: true,
verificationCode,
botLink
verificationCode,
botLink,
});
} catch (error) {
logger.error('Error initializing Telegram auth:', error);
if (error.message === 'Telegram уже привязан к этому аккаунту') {
return res.status(400).json({
return res.status(400).json({
success: false,
error: error.message
error: error.message,
});
}
res.status(500).json({
res.status(500).json({
success: false,
error: 'Failed to initialize Telegram auth'
error: 'Failed to initialize Telegram auth',
});
}
});
@@ -467,29 +475,29 @@ router.post('/telegram/init', async (req, res) => {
router.post('/email/init', async (req, res) => {
try {
const { email } = req.body;
if (!email || !email.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) {
return res.status(400).json({
success: false,
error: 'Некорректный формат email'
error: 'Некорректный формат email',
});
}
// Инициализация email аутентификации
const result = await emailAuth.initEmailAuth(req.session, email);
// Сохраняем сессию
await sessionService.saveSession(req.session);
return res.json({
success: true,
message: 'Код верификации отправлен на email'
message: 'Код верификации отправлен на email',
});
} catch (error) {
logger.error('Error in email auth initialization:', error);
res.status(500).json({
success: false,
error: 'Внутренняя ошибка сервера'
error: 'Внутренняя ошибка сервера',
});
}
});
@@ -499,21 +507,20 @@ router.get('/check', async (req, res) => {
try {
const authenticated = req.session.authenticated || false;
const authType = req.session.authType || null;
let identities = [];
let isAdmin = false;
if (authenticated && req.session.userId) {
// Если пользователь аутентифицирован, получаем его идентификаторы из БД
try {
identities = await identityService.getUserIdentities(req.session.userId);
// Проверяем роль пользователя
const roleResult = await db.query(
'SELECT role FROM users WHERE id = $1',
[req.session.userId]
);
const roleResult = await db.query('SELECT role FROM users WHERE id = $1', [
req.session.userId,
]);
if (roleResult.rows.length > 0) {
isAdmin = roleResult.rows[0].role === 'admin';
req.session.isAdmin = isAdmin;
@@ -522,15 +529,15 @@ router.get('/check', async (req, res) => {
logger.error(`[session/check] Error fetching identities: ${error.message}`);
}
}
// Проверяем, нужно ли создать новый гостевой ID
if (!authenticated && !req.session.guestId) {
req.session.guestId = crypto.randomBytes(16).toString('hex');
// Сохраняем сессию с новым гостевым ID
await sessionService.saveSession(req.session);
}
// Формируем ответ
const response = {
success: true,
@@ -539,9 +546,9 @@ router.get('/check', async (req, res) => {
guestId: req.session.guestId || null,
authType,
identitiesCount: identities.length,
isAdmin: isAdmin || false
isAdmin: isAdmin || false,
};
// Добавляем специфические поля в зависимости от типа аутентификации
if (authType === 'wallet') {
response.address = req.session.address || null;
@@ -556,13 +563,13 @@ router.get('/check', async (req, res) => {
response.telegramFirstName = req.session.telegramFirstName;
}
}
return res.json(response);
} catch (error) {
logger.error('[session/check] Error:', error);
return res.status(500).json({
success: false,
error: 'Internal server error'
error: 'Internal server error',
});
}
});
@@ -609,7 +616,7 @@ router.get('/check-access', requireAuth, async (req, res) => {
if (address) {
const isAdmin = await authService.checkAdminTokens(address);
// Обновляем сессию
req.session.isAdmin = isAdmin;
await sessionService.saveSession(req.session);
@@ -618,7 +625,7 @@ router.get('/check-access', requireAuth, async (req, res) => {
success: true,
isAdmin,
userId,
address
address,
});
}
@@ -626,9 +633,8 @@ router.get('/check-access', requireAuth, async (req, res) => {
success: true,
isAdmin: false,
userId,
address: null
address: null,
});
} catch (error) {
logger.error('Error checking access:', error);
return res.status(500).json({ error: 'Internal server error' });
@@ -639,22 +645,22 @@ router.get('/check-access', requireAuth, async (req, res) => {
router.post('/refresh-session', async (req, res) => {
try {
const { address } = req.body;
if (req.session && req.session.authenticated) {
logger.info('Обновление сессии для пользователя:', req.session.userId);
// Обновляем время жизни сессии
req.session.cookie.maxAge = 30 * 24 * 60 * 60 * 1000; // 30 дней
// Сохраняем обновленную сессию
await sessionService.saveSession(req.session);
return res.json({ success: true });
} else if (address) {
// Если сессия не аутентифицирована, но есть адрес
try {
const user = await identityService.findUserByIdentity('wallet', address.toLowerCase());
if (user) {
// Обновляем сессию
req.session.authenticated = true;
@@ -662,17 +668,17 @@ router.post('/refresh-session', async (req, res) => {
req.session.address = address.toLowerCase();
req.session.isAdmin = user.role === 'admin';
req.session.authType = 'wallet';
// Сохраняем обновленную сессию
await sessionService.saveSession(req.session);
return res.json({ success: true });
}
} catch (error) {
logger.error('Ошибка при проверке пользователя:', error);
}
}
// Если не удалось обновить сессию, возвращаем успех=false, но не ошибку
return res.json({ success: false });
} catch (error) {
@@ -685,82 +691,78 @@ router.post('/refresh-session', async (req, res) => {
router.post('/wallet', async (req, res) => {
try {
const { address, nonce, signature } = req.body;
if (!address || !nonce || !signature) {
return res.status(400).json({
success: false,
error: 'Missing required fields'
return res.status(400).json({
success: false,
error: 'Missing required fields',
});
}
// Сохраняем гостевые ID до аутентификации
const guestId = req.session.guestId;
const previousGuestId = req.session.previousGuestId;
// Формируем сообщение для проверки
const message = `Sign this message to authenticate with HB3 DApp: ${nonce}`;
// Проверяем подпись
const validSignature = await authService.verifySignature(message, signature, address);
if (!validSignature) {
return res.status(401).json({
success: false,
error: 'Invalid signature'
return res.status(401).json({
success: false,
error: 'Invalid signature',
});
}
// Получаем или создаем пользователя
const { userId } = await authService.findOrCreateUser(address);
// Проверяем наличие админских токенов
const isAdmin = await authService.checkAdminTokens(address);
// Обновляем роль пользователя в базе данных, если нужно
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]);
}
// Сохраняем идентификаторы
await identityService.saveIdentity(userId, 'wallet', address.toLowerCase(), true);
if (guestId) {
await identityService.saveIdentity(userId, 'guest', guestId, true);
}
if (previousGuestId && previousGuestId !== guestId) {
await identityService.saveIdentity(userId, 'guest', previousGuestId, true);
}
// Устанавливаем сессию
req.session.userId = userId;
req.session.address = address.toLowerCase();
req.session.authType = 'wallet';
req.session.authenticated = true;
req.session.isAdmin = isAdmin;
// Сохраняем сессию
await sessionService.saveSession(req.session);
// Связываем гостевые сообщения с пользователем
await sessionService.linkGuestMessages(req.session, userId);
// Возвращаем успешный ответ
return res.json({
success: true,
userId,
address,
isAdmin,
authenticated: true
authenticated: true,
});
} catch (error) {
logger.error('[wallet] Error:', error);
res.status(500).json({
success: false,
error: 'Server error during wallet authentication'
res.status(500).json({
success: false,
error: 'Server error during wallet authentication',
});
}
});
@@ -769,19 +771,19 @@ router.post('/wallet', async (req, res) => {
router.get('/identities', requireAuth, async (req, res) => {
try {
const { userId } = req.session;
// Получаем все идентификаторы пользователя
const identities = await identityService.getUserIdentities(userId);
res.json({
success: true,
identities
identities,
});
} catch (error) {
logger.error('Error getting user identities:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
error: 'Internal server error',
});
}
});
@@ -794,17 +796,17 @@ router.get('/check-session', async (req, res) => {
req.session.guestId = crypto.randomBytes(16).toString('hex');
await sessionService.saveSession(req.session);
}
res.json({
success: true,
guestId: req.session.guestId,
isAuthenticated: req.session.authenticated || false
isAuthenticated: req.session.authenticated || false,
});
} catch (error) {
logger.error('Error checking session:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
error: 'Internal server error',
});
}
});
@@ -813,21 +815,21 @@ router.get('/check-session', async (req, res) => {
router.get('/check-tokens/:address', async (req, res) => {
try {
const { address } = req.params;
// Получаем балансы токенов на всех сетях
const balances = await authService.getTokenBalances(address);
res.json({
success: true,
balances
balances,
});
} catch (error) {
logger.error('Error checking token balances:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
error: 'Internal server error',
});
}
});
module.exports = router;
module.exports = router;