357 lines
13 KiB
JavaScript
357 lines
13 KiB
JavaScript
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 };
|