Описание изменений

This commit is contained in:
2025-03-03 20:43:58 +03:00
parent 13e240f871
commit d36fc255d8
56 changed files with 4451 additions and 3390 deletions

357
backend/routes/auth.js Normal file
View File

@@ -0,0 +1,357 @@
const express = require('express');
const router = express.Router();
const { ethers } = require('ethers');
const crypto = require('crypto');
const db = require('../db');
const logger = require('../utils/logger');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
// Создайте лимитер для попыток аутентификации
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 минут
max: 20, // Увеличьте лимит с 5 до 20
standardHeaders: true,
legacyHeaders: false,
message: { error: 'Слишком много попыток аутентификации. Попробуйте позже.' }
});
// Маршрут для получения nonce для подписи
router.get('/nonce', async (req, res) => {
try {
const { address } = req.query;
// Удалите или закомментируйте эти логи
// console.log('Nonce request:', {
// address,
// sessionID: req.sessionID,
// session: req.session
// });
if (!address) {
return res.status(400).json({ error: 'Address is required' });
}
// Генерируем случайный nonce
const nonce = crypto.randomBytes(32).toString('hex');
// Создаем сообщение для подписи
const message = `Sign this message to authenticate with DApp for Business. Nonce: ${nonce}`;
// Сохраняем nonce в сессии
req.session.nonce = nonce;
req.session.pendingAddress = address;
// Получаем IP-адрес клиента
const clientIP = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
// Сохраняем IP-адрес в сессии при генерации nonce
req.session.clientIP = clientIP;
// Явно сохраняем сессию
req.session.save((err) => {
if (err) {
// Удалите или закомментируйте эти логи
// console.error('Error saving session:', err);
return res.status(500).json({ error: 'Failed to save session' });
}
// Удалите или закомментируйте
// console.log('Nonce saved in session:', {
// nonce,
// pendingAddress: address,
// sessionID: req.sessionID
// });
res.json({ message });
});
} catch (error) {
// Удалите или закомментируйте эти логи
// console.error('Error generating nonce:', error);
logger.error('Error generating nonce:', error);
res.status(500).json({ error: 'Failed to generate nonce' });
}
});
// Маршрут для верификации подписи
router.post('/verify', authLimiter, async (req, res) => {
try {
const { address, signature } = req.body;
if (!address || !signature) {
return res.status(400).json({ error: 'Address and signature are required' });
}
// Удалите или закомментируйте эти логи
// console.log('Verify request:', {
// address,
// signature,
// sessionID: req.sessionID,
// session: {
// nonce: req.session.nonce,
// pendingAddress: req.session.pendingAddress
// }
// });
// Получаем nonce из сессии
const nonce = req.session.nonce;
const pendingAddress = req.session.pendingAddress;
if (!nonce || !pendingAddress) {
return res.status(400).json({ error: 'No pending authentication request' });
}
// Получаем IP-адрес клиента
const clientIP = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
// Проверяем, что IP-адрес совпадает
if (req.session.clientIP !== clientIP) {
return res.status(400).json({ error: 'IP address mismatch' });
}
// Проверяем, что адрес совпадает с тем, для которого был сгенерирован nonce
if (pendingAddress.toLowerCase() !== address.toLowerCase()) {
return res.status(400).json({ error: 'Address mismatch' });
}
// Создаем сообщение для проверки подписи
const message = `Sign this message to authenticate with DApp for Business. Nonce: ${nonce}`;
// Восстанавливаем адрес из подписи
const recoveredAddress = ethers.verifyMessage(message, signature);
// Проверяем, что восстановленный адрес совпадает с предоставленным
if (recoveredAddress.toLowerCase() !== address.toLowerCase()) {
return res.status(400).json({ error: 'Invalid signature' });
}
// Проверяем, существует ли пользователь в базе данных
const user = await db.query('SELECT * FROM users WHERE address = $1', [address]);
let userId;
let isAdmin = false;
if (user.rows.length === 0) {
// Если пользователь не существует, создаем его
const newUser = await db.query(
'INSERT INTO users (address, created_at) VALUES ($1, NOW()) RETURNING id',
[address]
);
userId = newUser.rows[0].id;
} else {
userId = user.rows[0].id;
isAdmin = user.rows[0].is_admin || false;
}
// Устанавливаем состояние аутентификации в сессии
req.session.authenticated = true;
req.session.address = address;
req.session.isAdmin = isAdmin;
req.session.authType = 'wallet';
req.session.userId = userId;
// Удаляем nonce из сессии
delete req.session.nonce;
delete req.session.pendingAddress;
// Явно сохраняем сессию
req.session.save((err) => {
if (err) {
// Удалите или закомментируйте эти логи
// console.error('Error saving session:', err);
return res.status(500).json({ error: 'Failed to save session' });
}
// Удалите или закомментируйте
// console.log('Authentication successful:', {
// address,
// isAdmin,
// sessionID: req.sessionID
// });
res.json({
authenticated: true,
address,
isAdmin,
authType: 'wallet'
});
});
} catch (error) {
console.error('Error verifying signature:', error);
// Более подробная обработка ошибок
if (error.message.includes('invalid signature')) {
return res.status(400).json({
error: 'Недействительная подпись',
message: 'Подпись не соответствует адресу. Пожалуйста, попробуйте снова.'
});
}
if (error.message.includes('invalid address')) {
return res.status(400).json({
error: 'Недействительный адрес',
message: 'Указанный адрес имеет неверный формат.'
});
}
res.status(500).json({
error: 'Ошибка верификации подписи',
message: 'Не удалось проверить подпись. Пожалуйста, попробуйте снова позже.'
});
}
});
// Маршрут для проверки состояния аутентификации
router.get('/check', (req, res) => {
// Удалите или закомментируйте эти логи
// console.log('Session check:', {
// session: req.session,
// authenticated: req.session.authenticated
// });
if (req.session.authenticated) {
res.json({
authenticated: true,
address: req.session.address,
isAdmin: req.session.isAdmin,
authType: req.session.authType
});
} else {
res.json({
authenticated: false,
address: null,
isAdmin: false,
authType: null
});
}
});
// Маршрут для выхода из системы
router.post('/logout', (req, res) => {
req.session.destroy(err => {
if (err) {
// Удалите или закомментируйте эти логи
// console.error('Error destroying session:', err);
return res.status(500).json({ error: 'Failed to logout' });
}
res.json({ success: true });
});
});
// Маршрут для авторизации через Telegram
router.get('/telegram', (req, res) => {
// Генерируем случайный токен для авторизации
const token = crypto.randomBytes(32).toString('hex');
// Сохраняем токен в сессии
req.session.telegramToken = token;
// Создаем URL для авторизации через Telegram
const botName = process.env.TELEGRAM_BOT_NAME || 'YourBotName';
const authUrl = `https://t.me/${botName}?start=${token}`;
res.json({ authUrl });
});
// Маршрут для авторизации через Email
router.post('/email', async (req, res) => {
try {
const { email } = req.body;
if (!email) {
return res.status(400).json({ error: 'Email is required' });
}
// Генерируем код подтверждения
const verificationCode = Math.floor(100000 + Math.random() * 900000).toString();
// Сохраняем код в сессии
req.session.emailVerificationCode = verificationCode;
req.session.pendingEmail = email;
// В реальном приложении здесь нужно отправить email с кодом подтверждения
// Удалите или закомментируйте эти логи
// console.log(`Verification code for ${email}: ${verificationCode}`);
res.json({ success: true, message: 'Verification code sent' });
} catch (error) {
// Удалите или закомментируйте эти логи
// console.error('Error sending verification code:', error);
logger.error('Error sending verification code:', error);
res.status(500).json({ error: 'Failed to send verification code' });
}
});
// Маршрут для проверки кода подтверждения Email
router.post('/email/verify', async (req, res) => {
try {
const { email, code } = req.body;
if (!email || !code) {
return res.status(400).json({ error: 'Email and code are required' });
}
// Получаем код из сессии
const verificationCode = req.session.emailVerificationCode;
const pendingEmail = req.session.pendingEmail;
if (!verificationCode || !pendingEmail) {
return res.status(400).json({ error: 'No pending verification' });
}
// Проверяем, что email совпадает с тем, для которого был сгенерирован код
if (pendingEmail !== email) {
return res.status(400).json({ error: 'Email mismatch' });
}
// Проверяем код
if (verificationCode !== code) {
return res.status(400).json({ error: 'Invalid verification code' });
}
// Проверяем, существует ли пользователь в базе данных
const user = await db.query('SELECT * FROM users WHERE email = $1', [email]);
let userId;
let isAdmin = false;
if (user.rows.length === 0) {
// Если пользователь не существует, создаем его
const newUser = await db.query(
'INSERT INTO users (email, created_at) VALUES ($1, NOW()) RETURNING id',
[email]
);
userId = newUser.rows[0].id;
} else {
userId = user.rows[0].id;
isAdmin = user.rows[0].is_admin || false;
}
// Устанавливаем состояние аутентификации в сессии
req.session.isAuthenticated = true;
req.session.authenticated = true;
req.session.address = email;
req.session.userId = userId;
req.session.isAdmin = isAdmin;
req.session.authType = 'email';
// Удаляем код из сессии
delete req.session.emailVerificationCode;
delete req.session.pendingEmail;
res.json({
authenticated: true,
address: email,
isAdmin,
authType: 'email'
});
} catch (error) {
// Удалите или закомментируйте эти логи
// console.error('Error verifying email code:', error);
logger.error('Error verifying email code:', error);
res.status(500).json({ error: 'Failed to verify email code' });
}
});
module.exports = { router };