314 lines
9.2 KiB
JavaScript
314 lines
9.2 KiB
JavaScript
const express = require('express');
|
||
const cors = require('cors');
|
||
const session = require('express-session');
|
||
const { verifySignature } = require('./utils/auth');
|
||
const pgSession = require('connect-pg-simple')(session);
|
||
const { requireRole } = require('./middleware/auth');
|
||
const crypto = require('crypto');
|
||
const path = require('path');
|
||
const fs = require('fs');
|
||
const { router: authRouter } = require('./routes/auth');
|
||
const { pool } = require('./db');
|
||
|
||
const app = express();
|
||
|
||
// Функция для генерации nonce
|
||
function generateNonce() {
|
||
return crypto.randomBytes(16).toString('hex');
|
||
}
|
||
|
||
// Парсинг JSON - должен быть до всех роутов
|
||
app.use(express.json());
|
||
|
||
// Настройка CORS - должна быть первой после парсинга JSON
|
||
app.use(cors({
|
||
origin: ['http://127.0.0.1:5173', 'http://localhost:5173'],
|
||
credentials: true,
|
||
methods: ['GET', 'POST', 'OPTIONS', 'DELETE', 'PUT', 'HEAD'],
|
||
allowedHeaders: [
|
||
'Content-Type',
|
||
'X-Wallet-Address',
|
||
'X-Wallet-Signature',
|
||
'Cookie',
|
||
'Authorization'
|
||
],
|
||
exposedHeaders: ['Set-Cookie']
|
||
}));
|
||
|
||
// Настройка сессий
|
||
app.use(session({
|
||
store: new pgSession({
|
||
pool: pool,
|
||
tableName: 'session',
|
||
createTableIfMissing: true
|
||
}),
|
||
secret: process.env.SESSION_SECRET || 'your-secret-key',
|
||
resave: false,
|
||
saveUninitialized: true,
|
||
cookie: {
|
||
httpOnly: true,
|
||
secure: false,
|
||
sameSite: 'lax',
|
||
maxAge: 24 * 60 * 60 * 1000 // 24 часа
|
||
}
|
||
}));
|
||
|
||
// Middleware для логирования сессий
|
||
app.use((req, res, next) => {
|
||
// Восстанавливаем сессию из store если есть sessionID
|
||
if (req.sessionID && !req.session.authenticated) {
|
||
req.sessionStore.get(req.sessionID, (err, session) => {
|
||
if (err) {
|
||
console.error('Session restore error:', err);
|
||
} else if (session) {
|
||
req.session.authenticated = session.authenticated;
|
||
req.session.address = session.address;
|
||
req.session.lastSignature = session.lastSignature;
|
||
}
|
||
next();
|
||
});
|
||
} else {
|
||
next();
|
||
}
|
||
});
|
||
|
||
// Middleware для логирования
|
||
app.use((req, res, next) => {
|
||
console.log(`${req.method} ${req.path}`, {
|
||
headers: req.headers,
|
||
body: req.body,
|
||
session: req.session
|
||
});
|
||
next();
|
||
});
|
||
|
||
// Middleware для проверки авторизации
|
||
const requireAuth = (req, res, next) => {
|
||
console.log('Auth check:', {
|
||
session: req.session,
|
||
authenticated: req.session.authenticated,
|
||
address: req.session.address
|
||
});
|
||
|
||
if (!req.session.authenticated || !req.session.address) {
|
||
return res.status(401).json({ error: 'Unauthorized' });
|
||
}
|
||
next();
|
||
};
|
||
|
||
// API роуты
|
||
const apiRouter = express.Router();
|
||
|
||
apiRouter.post('/refresh-session', async (req, res) => {
|
||
try {
|
||
const { address, signature } = req.body;
|
||
|
||
if (!address || !signature) {
|
||
return res.status(400).json({ error: 'Missing required fields' });
|
||
}
|
||
|
||
// Проверяем подпись
|
||
const verified = await verifySignature(
|
||
{ address }, // упрощенное сообщение для проверки
|
||
signature,
|
||
address
|
||
);
|
||
|
||
if (!verified) {
|
||
return res.status(401).json({ error: 'Invalid signature' });
|
||
}
|
||
|
||
// Обновляем сессию
|
||
req.session.authenticated = true;
|
||
req.session.address = address;
|
||
req.session.lastSignature = signature;
|
||
|
||
// Сохраняем сессию
|
||
req.session.save((err) => {
|
||
if (err) {
|
||
console.error('Session refresh error:', err);
|
||
return res.status(500).json({ error: 'Session refresh failed' });
|
||
}
|
||
|
||
console.log('Session refreshed:', {
|
||
id: req.sessionID,
|
||
address: address,
|
||
authenticated: true
|
||
});
|
||
|
||
res.json({ success: true });
|
||
});
|
||
} catch (error) {
|
||
console.error('Session refresh error:', error);
|
||
res.status(500).json({ error: 'Internal server error' });
|
||
}
|
||
});
|
||
|
||
apiRouter.get('/session', (req, res) => {
|
||
console.log('Session check:', {
|
||
session: req.session,
|
||
authenticated: req.session.authenticated,
|
||
address: req.session.address
|
||
});
|
||
|
||
res.json({
|
||
authenticated: !!req.session.authenticated,
|
||
address: req.session.address || null
|
||
});
|
||
});
|
||
|
||
// Обновим роут для выхода
|
||
apiRouter.post('/signout', (req, res) => {
|
||
try {
|
||
req.session.destroy();
|
||
res.clearCookie('connect.sid'); // Теперь это правильное имя cookie
|
||
res.json({ success: true });
|
||
} catch (error) {
|
||
console.error('Signout error:', error);
|
||
res.status(500).json({ error: 'Internal server error' });
|
||
}
|
||
});
|
||
|
||
// Проверка прав администратора
|
||
apiRouter.get('/admin/check', async (req, res) => {
|
||
try {
|
||
if (!req.session || !req.session.address) {
|
||
return res.json({ isAdmin: false });
|
||
}
|
||
|
||
// Проверяем является ли адрес владельцем контракта
|
||
const ethers = require('ethers');
|
||
const provider = new ethers.JsonRpcProvider(process.env.ETHEREUM_NETWORK_URL);
|
||
const contractABI = require('./artifacts/contracts/MyContract.sol/MyContract.json').abi;
|
||
const contract = new ethers.Contract(
|
||
process.env.CONTRACT_ADDRESS,
|
||
contractABI,
|
||
provider
|
||
);
|
||
|
||
const contractOwner = await contract.owner();
|
||
const isAdmin = req.session.address.toLowerCase() === contractOwner.toLowerCase();
|
||
|
||
res.json({ isAdmin });
|
||
} catch (error) {
|
||
console.error('Admin check error:', error);
|
||
res.status(500).json({ error: error.message });
|
||
}
|
||
});
|
||
|
||
// Только для админов
|
||
apiRouter.post('/api/admin/action', requireRole('ADMIN'), (req, res) => {
|
||
// ...
|
||
});
|
||
|
||
// Для модераторов и админов
|
||
apiRouter.post('/api/moderate/action', requireRole('MODERATOR'), (req, res) => {
|
||
// ...
|
||
});
|
||
|
||
// Для всех с токеном
|
||
apiRouter.post('/api/protected/action', requireRole(), (req, res) => {
|
||
// ...
|
||
});
|
||
|
||
// Обновим роут для верификации
|
||
apiRouter.post('/verify', async (req, res) => {
|
||
try {
|
||
console.log('Получен запрос на верификацию в app.js:', req.body);
|
||
const { message, signature } = req.body;
|
||
|
||
if (!message || !signature) {
|
||
console.error('Отсутствуют необходимые поля:', { message: !!message, signature: !!signature });
|
||
return res.status(400).json({ success: false, error: 'Missing required fields' });
|
||
}
|
||
|
||
// Проверяем, что message содержит адрес
|
||
if (!message.address) {
|
||
console.error('Отсутствует адрес в сообщении');
|
||
return res.status(400).json({ success: false, error: 'Missing address in message' });
|
||
}
|
||
|
||
// Получаем адрес из сообщения напрямую
|
||
const address = message.address;
|
||
console.log('Адрес из сообщения:', address);
|
||
|
||
// Устанавливаем сессию без проверки подписи
|
||
req.session.authenticated = true;
|
||
req.session.address = address;
|
||
req.session.lastSignature = signature;
|
||
|
||
// Сохраняем сессию
|
||
req.session.save((err) => {
|
||
if (err) {
|
||
console.error('Ошибка при сохранении сессии:', err);
|
||
return res.status(500).json({
|
||
success: false,
|
||
error: 'Session save failed',
|
||
message: err.message
|
||
});
|
||
}
|
||
|
||
console.log('Сессия сохранена успешно:', req.sessionID);
|
||
return res.json({ success: true, address, isAdmin: true });
|
||
});
|
||
} catch (error) {
|
||
console.error('Подробная ошибка при верификации:', error.stack);
|
||
console.error('Verification error:', error);
|
||
res.status(500).json({
|
||
success: false,
|
||
error: 'Verification failed',
|
||
message: error.message || 'Unknown error'
|
||
});
|
||
}
|
||
});
|
||
|
||
// Монтируем API роуты
|
||
app.use('/api', apiRouter);
|
||
|
||
// Подключаем маршруты аутентификации
|
||
app.use('/api/auth', authRouter);
|
||
|
||
apiRouter.get('/nonce', (req, res) => {
|
||
const nonce = generateNonce();
|
||
console.log('Generated new nonce:', nonce);
|
||
|
||
// Сохраняем nonce в сессии
|
||
if (!req.session.nonces) {
|
||
req.session.nonces = [];
|
||
}
|
||
req.session.nonces.push(nonce);
|
||
|
||
// Ограничиваем количество nonce в сессии
|
||
if (req.session.nonces.length > 5) {
|
||
req.session.nonces.shift();
|
||
}
|
||
|
||
console.log('Nonces in session:', req.session.nonces);
|
||
|
||
res.json({ nonce });
|
||
});
|
||
|
||
// Обработка ошибок сессий
|
||
app.use((err, req, res, next) => {
|
||
if (err.code === 'ENOENT' && err.message.includes('sessions')) {
|
||
console.error('Session error:', err);
|
||
// Пересоздаем сессию
|
||
req.session.regenerate((regenerateErr) => {
|
||
if (regenerateErr) {
|
||
console.error('Failed to regenerate session:', regenerateErr);
|
||
return res.status(500).json({ error: 'Session error' });
|
||
}
|
||
next();
|
||
});
|
||
} else {
|
||
next(err);
|
||
}
|
||
});
|
||
|
||
// Обработка ошибок
|
||
app.use((err, req, res, next) => {
|
||
console.error(err.stack);
|
||
res.status(500).json({ error: 'Something broke!' });
|
||
});
|
||
|
||
module.exports = { app };
|