Files
DLE/backend/routes/auth.js

661 lines
23 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 { checkRole, requireAuth } = require('../middleware/auth');
const { pool } = require('../db');
const { verifySignature, checkAccess, findOrCreateUser } = require('../utils/auth');
const authService = require('../services/auth-service');
// Создайте лимитер для попыток аутентификации
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;
// Важно: сохраняем сессию перед отправкой ответа
await new Promise((resolve, reject) => {
req.session.save(err => {
if (err) reject(err);
else resolve();
});
});
console.log('Сессия после генерации nonce:', req.session);
console.log('Сессия после сохранения:', req.session);
return res.json({ nonce });
} catch (error) {
console.error('Error generating 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 } = req.body;
console.log('Verify request: address=' + address + ', signature=' + signature.substring(0, 10) + '...');
console.log('Session data: nonce=' + req.session.authNonce + ', pendingAddress=' + req.session.pendingAddress);
// Проверяем, что nonce и адрес совпадают с сохраненными в сессии
if (!req.session.authNonce || !req.session.pendingAddress || req.session.pendingAddress !== address) {
console.error(`Invalid session or address mismatch: nonce=${req.session.authNonce}, pendingAddress=${req.session.pendingAddress}, address=${address}`);
return res.status(401).json({ error: 'Invalid session or address mismatch' });
}
// Проверяем подпись
const isValid = await verifySignature(req.session.authNonce, signature, address);
console.log('Signature verification result:', isValid);
if (!isValid) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Находим или создаем пользователя
console.log('Finding or creating user for address:', address);
const { userId, isAdmin } = await findOrCreateUser(address);
console.log('User found/created:', { userId, isAdmin });
// Очищаем nonce и pendingAddress из сессии
const nonce = req.session.authNonce;
req.session.authNonce = null;
req.session.pendingAddress = null;
// Устанавливаем пользователя в сессии
req.session.userId = userId;
req.session.address = address;
req.session.isAdmin = isAdmin;
req.session.authenticated = true;
// Сохраняем сессию
await new Promise((resolve, reject) => {
req.session.save(err => {
if (err) {
console.error('Error saving session:', err);
reject(err);
} else {
console.log('Session saved successfully');
resolve();
}
});
});
console.log('Authentication successful for user:', { userId, address, isAdmin });
return res.json({
authenticated: true,
userId,
address,
isAdmin,
authType: 'wallet'
});
} catch (error) {
console.error('Error verifying signature:', error);
return res.status(500).json({ error: 'Internal server error' });
}
});
// Аутентификация через Telegram
router.post('/telegram', async (req, res) => {
try {
const { telegramId, authData } = req.body;
// Здесь должна быть проверка данных от Telegram
// Получаем или создаем пользователя
let userId = await authService.getUserIdByIdentity('telegram', telegramId);
if (!userId) {
// Создаем нового пользователя
const userResult = await db.query(
'INSERT INTO users (created_at, role_id) VALUES (NOW(), (SELECT id FROM roles WHERE name = $1)) RETURNING id',
['user']
);
userId = userResult.rows[0].id;
// Добавляем идентификатор Telegram
await db.query(
'INSERT INTO user_identities (user_id, identity_type, identity_value, created_at) VALUES ($1, $2, $3, NOW())',
[userId, 'telegram', telegramId]
);
}
// Проверяем связанные аккаунты
const identities = await authService.getAllUserIdentities(userId);
// Если есть связанный кошелек, проверяем токены
if (identities.wallet) {
await authService.checkTokensAndUpdateRole(identities.wallet);
}
// Получаем текущую роль
const isAdmin = await authService.isAdmin(userId);
// Устанавливаем сессию
req.session.userId = userId;
req.session.telegramId = telegramId;
req.session.authType = 'telegram';
req.session.authenticated = true;
res.json({
authenticated: true,
userId,
isAdmin,
authType: 'telegram',
identities
});
} catch (error) {
logger.error(`Telegram auth error: ${error.message}`);
res.status(500).json({ error: 'Ошибка сервера' });
}
});
// Аутентификация через Email
router.post('/email', async (req, res) => {
try {
const { email, verificationCode } = req.body;
// Здесь должна быть проверка кода подтверждения
// Получаем или создаем пользователя
let userId = await authService.getUserIdByIdentity('email', email);
if (!userId) {
// Создаем нового пользователя
const userResult = await db.query(
'INSERT INTO users (created_at, role_id) VALUES (NOW(), (SELECT id FROM roles WHERE name = $1)) RETURNING id',
['user']
);
userId = userResult.rows[0].id;
// Добавляем идентификатор Email
await db.query(
'INSERT INTO user_identities (user_id, identity_type, identity_value, created_at) VALUES ($1, $2, $3, NOW())',
[userId, 'email', email]
);
}
// Проверяем связанные аккаунты
const identities = await authService.getAllUserIdentities(userId);
// Если есть связанный кошелек, проверяем токены
if (identities.wallet) {
await authService.checkTokensAndUpdateRole(identities.wallet);
}
// Получаем текущую роль
const isAdmin = await authService.isAdmin(userId);
// Устанавливаем сессию
req.session.userId = userId;
req.session.email = email;
req.session.authType = 'email';
req.session.authenticated = true;
res.json({
authenticated: true,
userId,
isAdmin,
authType: 'email',
identities
});
} catch (error) {
logger.error(`Email auth error: ${error.message}`);
res.status(500).json({ error: 'Ошибка сервера' });
}
});
// Связывание аккаунтов
router.post('/link-identity', async (req, res) => {
try {
if (!req.session || !req.session.userId) {
return res.status(401).json({ error: 'Требуется аутентификация' });
}
const { identityType, identityValue } = req.body;
// Проверяем, не привязан ли уже этот идентификатор к другому пользователю
const existingUserId = await authService.getUserIdByIdentity(identityType, identityValue);
if (existingUserId && existingUserId !== req.session.userId) {
return res.status(400).json({ error: 'Этот идентификатор уже привязан к другому аккаунту' });
}
// Добавляем новый идентификатор
if (!existingUserId) {
await db.query(
'INSERT INTO user_identities (user_id, identity_type, identity_value, created_at) VALUES ($1, $2, $3, NOW())',
[req.session.userId, identityType, identityValue]
);
}
// Если добавлен кошелек, проверяем токены
if (identityType === 'wallet') {
await authService.checkTokensAndUpdateRole(identityValue);
}
// Получаем все идентификаторы пользователя
const identities = await authService.getAllUserIdentities(req.session.userId);
// Получаем текущую роль
const isAdmin = await authService.isAdmin(req.session.userId);
res.json({
success: true,
identities,
isAdmin
});
} catch (error) {
logger.error(`Link identity error: ${error.message}`);
res.status(500).json({ 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' });
}
});
// Добавьте этот маршрут в routes/auth.js
router.post('/refresh-session', async (req, res) => {
try {
const { address } = req.body;
if (!address) {
return res.status(400).json({ error: 'Address is required' });
}
logger.info(`Получен запрос на обновление сессии для адреса: ${address}`);
// Проверяем доступ пользователя
const accessInfo = await checkAccess(address);
if (!accessInfo.hasAccess) {
return res.status(401).json({ error: 'Unauthorized' });
}
// Устанавливаем данные сессии
req.session.authenticated = true;
req.session.address = address;
req.session.userId = accessInfo.userId;
req.session.isAdmin = accessInfo.isAdmin;
req.session.authType = 'wallet';
await req.session.save();
res.json({
authenticated: true,
address,
isAdmin: accessInfo.isAdmin,
authType: 'wallet'
});
} catch (error) {
logger.error(`Error refreshing session: ${error.message}`);
res.status(500).json({ error: 'Internal server error' });
}
});
// Маршрут для обновления статуса администратора
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.get('/debug-session', (req, res) => {
res.json({
sessionID: req.sessionID,
session: req.session,
authenticated: req.session ? req.session.authenticated : undefined,
address: req.session ? req.session.address : undefined,
userId: req.session ? req.session.userId : undefined,
isAdmin: req.session ? req.session.isAdmin : undefined,
role: req.session ? req.session.role : undefined
});
});
// Маршрут для проверки сессии
router.get('/session-debug', (req, res) => {
console.log('Текущая сессия:', {
id: req.sessionID,
session: req.session,
cookie: req.session.cookie
});
res.json({
sessionID: req.sessionID,
authenticated: req.session.authenticated,
address: req.session.address,
userId: req.session.userId,
isAdmin: req.session.isAdmin,
role: req.session.role,
cookie: req.session.cookie
});
});
// Маршрут для проверки содержимого таблицы сессий
router.get('/check-sessions', async (req, res) => {
try {
const result = await pool.query('SELECT * FROM sessions');
res.json({
currentSessionID: req.sessionID,
sessions: result.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;