Files
DLE/backend/routes/auth.js

565 lines
20 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 { checkIfAdmin } = require('../utils/access-check');
const { checkRole, requireAuth } = require('../middleware/auth');
const { pool } = require('../db');
const { verifySignature, checkAccess, findOrCreateUser } = require('../utils/auth');
// Создайте лимитер для попыток аутентификации
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;
if (!address) {
return res.status(400).json({ error: 'Address is required' });
}
// Генерируем nonce
const nonce = crypto.randomBytes(16).toString('hex');
// Сохраняем nonce в сессии
req.session.authNonce = nonce;
req.session.pendingAddress = address.toLowerCase();
console.log('Сгенерирован nonce для адреса:', address);
console.log('Сессия после генерации nonce:', req.session);
// Сохраняем сессию и ждем завершения
await new Promise((resolve, reject) => {
req.session.save((err) => {
if (err) {
console.error('Ошибка при сохранении сессии:', err);
reject(err);
} else {
resolve();
}
});
});
// Проверяем, что nonce сохранился
console.log('Сессия после сохранения:', req.session);
return res.json({ nonce });
} catch (error) {
console.error('Ошибка при генерации nonce:', error);
return res.status(500).json({ error: 'Internal server error' });
}
});
// Функция для проверки роли пользователя
async function checkUserRole(address, req) {
try {
const lowerCaseAddress = address.toLowerCase();
// Проверяем наличие токена доступа в базе данных
const result = await db.query(
'SELECT role FROM access_tokens WHERE LOWER(wallet_address) = $1 AND expires_at > NOW()',
[lowerCaseAddress]
);
if (result.rows.length > 0) {
// Если есть активный токен, проверяем роль
const role = result.rows[0].role;
return role === 'ADMIN';
}
// Если нет токена, проверяем адрес администратора из переменных окружения
const adminAddresses = (process.env.ADMIN_ADDRESSES || '')
.split(',')
.map((a) => a.toLowerCase());
return adminAddresses.includes(lowerCaseAddress);
} catch (error) {
console.error('Ошибка при проверке роли пользователя:', error);
return false;
}
}
// Верификация подписи
router.post('/verify', async (req, res) => {
try {
const { address, signature, message, nonce } = req.body;
console.log('Верификация подписи:', { address, signature, message });
console.log('Сессия при верификации:', req.session);
if (!address || !signature || !message) {
return res.status(400).json({ error: 'Address, signature and message are required' });
}
// Проверяем наличие nonce в сессии
if (!req.session.authNonce || !req.session.pendingAddress) {
console.error('Сессия не содержит nonce или pendingAddress:', req.session);
// Проверяем наличие nonce в заголовке
const headerNonce = req.headers['x-auth-nonce'];
if (headerNonce) {
console.log('Найден nonce в заголовке:', headerNonce);
req.session.authNonce = headerNonce;
req.session.pendingAddress = address.toLowerCase();
}
// Если в запросе есть nonce в сообщении, извлекаем его
let extractedNonce = null;
if (message) {
const match = message.match(/nonce: ([a-f0-9]+)/);
if (match && match[1]) {
extractedNonce = match[1];
console.log('Извлечен nonce из сообщения:', extractedNonce);
// Устанавливаем nonce в сессию
req.session.authNonce = extractedNonce;
req.session.pendingAddress = address.toLowerCase();
// Сохраняем сессию
await new Promise((resolve) => {
req.session.save((err) => {
if (err) console.error('Ошибка при сохранении сессии:', err);
resolve();
});
});
}
}
}
// Формируем ожидаемое сообщение
const expectedMessage = `Подтвердите вход в DApp for Business с nonce: ${req.session.authNonce}`;
// Проверяем, что адрес совпадает с ожидаемым
if (req.session.pendingAddress && req.session.pendingAddress.toLowerCase() !== address.toLowerCase()) {
console.error('Адрес не совпадает с ожидаемым:', {
expected: req.session.pendingAddress,
received: address,
});
return res.status(400).json({ error: 'Invalid address' });
}
let verified = false;
try {
// Проверяем подпись с использованием ethers.js
const recoveredAddress = ethers.verifyMessage(expectedMessage, signature);
if (recoveredAddress.toLowerCase() !== address.toLowerCase()) {
console.error('Неверная подпись:', {
expected: address.toLowerCase(),
recovered: recoveredAddress.toLowerCase(),
});
return res.status(400).json({ error: 'Invalid signature' });
}
verified = true;
console.log('Подпись успешно проверена');
} catch (error) {
console.error('Ошибка при проверке подписи:', error);
return res.status(400).json({ error: 'Invalid signature format' });
}
// Если подпись верна, аутентифицируем пользователя
if (verified) {
// Найдем или создадим пользователя
const user = await findOrCreateUser(address, 'wallet');
// Обновляем сессию
req.session.authenticated = true;
req.session.address = address;
req.session.userId = user.id;
req.session.authType = 'wallet';
req.session.isAdmin = user.is_admin;
req.session.role = user.role;
req.session.authChannel = 'web';
req.session.language = req.body.language || 'en';
// Удаляем временные данные
delete req.session.authNonce;
delete req.session.pendingAddress;
// Сохраняем сессию
await new Promise((resolve, reject) => {
req.session.save(err => {
if (err) {
console.error('Ошибка при сохранении сессии:', err);
reject(err);
} else {
resolve();
}
});
});
console.log('Аутентификация успешна:', {
address,
isAdmin: user.is_admin,
userId: user.id,
role: user.role
});
res.json({
authenticated: true,
address,
isAdmin: user.is_admin,
role: user.role
});
} else {
res.status(401).json({ error: 'Invalid signature' });
}
} catch (error) {
console.error('Authentication error:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Проверка текущей сессии
router.get('/check', (req, res) => {
console.log('Сессия при проверке:', req.session);
// Если сессия существует и пользователь аутентифицирован
if (req.session && req.session.authenticated) {
res.json({
authenticated: true,
address: req.session.address,
isAdmin: req.session.isAdmin || false,
role: req.session.role || 'USER'
});
} else {
res.json({
authenticated: false,
address: null,
isAdmin: false,
authType: null
});
}
});
// Обработчик выхода из системы
router.post('/logout', (req, res) => {
try {
// Сохраняем sessionID перед удалением сессии
const sessionID = req.sessionID;
// Удаляем сессию из хранилища
req.session.destroy(async (err) => {
if (err) {
console.error('Ошибка при удалении сессии:', err);
return res.status(500).json({ error: 'Internal server error' });
}
try {
// Удаляем запись из базы данных
await db.query('DELETE FROM sessions WHERE sid = $1', [sessionID]);
console.log(`Сессия ${sessionID} удалена из базы данных`);
} catch (dbErr) {
console.error('Ошибка при удалении сессии из базы данных:', dbErr);
}
// Очищаем cookie
res.clearCookie('dapp.sid');
res.json({ success: true });
});
} catch (error) {
console.error('Logout error:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Маршрут для авторизации через 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' });
}
});
// Добавляем маршрут для проверки прав доступа
router.get('/check-access', requireAuth, (req, res) => {
try {
// Получаем информацию о пользователе
const userData = {
address: req.session.address,
isAdmin: req.session.isAdmin || false,
roles: req.session.roles || [],
authenticated: true,
};
// Проверяем доступ к различным разделам
const access = {
dashboard: true, // Все аутентифицированные пользователи имеют доступ к панели управления
admin: userData.isAdmin, // Только администраторы имеют доступ к админке
contracts: userData.roles.includes('CONTRACT_MANAGER') || userData.isAdmin,
users: userData.roles.includes('USER_MANAGER') || userData.isAdmin,
};
res.json({
user: userData,
access: access,
});
} catch (error) {
console.error('Ошибка при проверке прав доступа:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Упрощенный маршрут для обновления сессии
router.post('/refresh-session', async (req, res) => {
try {
const { address } = req.body;
if (!address) {
return res.status(400).json({ success: false, message: 'Адрес не указан' });
}
console.log(`Получен запрос на обновление сессии для адреса: ${address}`);
// Проверяем, существует ли пользователь в базе данных
const userResult = await pool.query('SELECT * FROM users WHERE address = $1', [
address.toLowerCase(),
]);
let user = null;
if (userResult.rows.length > 0) {
user = userResult.rows[0];
console.log(`Найден пользователь: ${user.id}`);
} else {
console.log(`Пользователь с адресом ${address} не найден`);
}
// Обновляем сессию
req.session.authenticated = true;
req.session.address = address.toLowerCase();
if (user) {
req.session.userId = user.id;
req.session.isAdmin = user.is_admin || false;
req.session.role = user.is_admin ? 'ADMIN' : 'USER';
} else {
// Если пользователь не найден в базе, проверяем через переменные окружения
const adminAddresses = (process.env.ADMIN_ADDRESSES || '')
.split(',')
.map((a) => a.toLowerCase());
const isAdmin = adminAddresses.includes(address.toLowerCase());
req.session.isAdmin = isAdmin;
req.session.role = isAdmin ? 'ADMIN' : 'USER';
}
// Сохраняем сессию
await new Promise((resolve, reject) => {
req.session.save((err) => {
if (err) {
console.error('Ошибка при сохранении сессии:', err);
reject(err);
} else {
resolve();
}
});
});
console.log('Сессия обновлена:', req.session);
return res.json({
success: true,
message: 'Сессия обновлена',
user: {
id: user ? user.id : null,
address: address.toLowerCase(),
isAdmin: req.session.isAdmin,
role: req.session.role,
},
});
} catch (error) {
console.error('Ошибка при обновлении сессии:', error);
return res.status(500).json({ success: false, message: 'Ошибка сервера' });
}
});
// Маршрут для обновления статуса администратора
router.post('/update-admin-status', async (req, res) => {
try {
const { address, isAdmin } = req.body;
if (!address) {
return res.status(400).json({ error: 'Address is required' });
}
console.log(`Запрос на обновление статуса администратора для адреса ${address} на ${isAdmin}`);
// Проверяем, существует ли пользователь
const userResult = await db.query('SELECT * FROM users WHERE address = $1', [
address.toLowerCase(),
]);
if (userResult.rows.length === 0) {
// Если пользователь не найден, создаем его
await db.query('INSERT INTO users (address, is_admin, created_at) VALUES ($1, $2, NOW())', [
address.toLowerCase(),
isAdmin,
]);
console.log(
`Создан новый пользователь с адресом ${address} и статусом администратора ${isAdmin}`
);
} else {
// Если пользователь найден, обновляем его статус
await db.query('UPDATE users SET is_admin = $1 WHERE address = $2', [
isAdmin,
address.toLowerCase(),
]);
console.log(
`Обновлен статус администратора для пользователя с адресом ${address} на ${isAdmin}`
);
}
res.json({ success: true });
} catch (error) {
console.error('Ошибка при обновлении статуса администратора:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Маршрут для проверки структуры таблицы users
router.get('/check-db-structure', async (req, res) => {
try {
// Получаем информацию о таблице users
const tableInfo = await pool.query(`
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_name = 'users'
`);
res.json({
tableStructure: tableInfo.rows,
});
} catch (error) {
console.error('Ошибка при получении структуры базы данных:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Добавьте обработку ошибок
router.use((err, req, res, next) => {
console.error('Auth route error:', err);
res.status(500).json({ success: false, message: 'Ошибка сервера' });
});
module.exports = { router };