require('dotenv').config(); const express = require('express'); const cors = require('cors'); const { SiweMessage, generateNonce } = require('siwe'); const { ethers } = require('ethers'); const TelegramBotService = require('./services/telegramBot'); const EmailBotService = require('./services/emailBot'); const { initializeVectorStore } = require('./services/vectorStore'); const session = require('express-session'); const { app, nonceStore } = require('./app'); const usersRouter = require('./routes/users'); const { router: authRouter } = require('./routes/auth'); const contractsRouter = require('./routes/contracts'); const accessRouter = require('./routes/access'); const chatRouter = require('./routes/chat'); const path = require('path'); const axios = require('axios'); const { ChatOllama } = require('@langchain/ollama'); const { getVectorStore } = require('./services/vectorStore'); const debugRouter = require('./routes/debug'); const identitiesRouter = require('./routes/identities'); const kanbanRouter = require('./routes/kanban'); const { pool } = require('./db'); const fs = require('fs'); const pgSession = require('connect-pg-simple')(session); const sessionStore = new pgSession({ pool: pool, tableName: 'session', createTableIfMissing: true }); const helmet = require('helmet'); const csrf = require('csurf'); const PORT = process.env.PORT || 8000; console.log('Начало выполнения server.js'); console.log('Переменная окружения PORT:', process.env.PORT); console.log('Используемый порт:', process.env.PORT || 8000); // Инициализация сервисов let telegramBot; let emailBot; // Получаем ABI из артефактов const contractArtifact = require('./artifacts/contracts/MyContract.sol/MyContract.json'); const contractABI = contractArtifact.abi; // Добавим логирование для отладки контракта const provider = new ethers.JsonRpcProvider(process.env.ETHEREUM_NETWORK_URL); console.log('Provider URL:', process.env.ETHEREUM_NETWORK_URL); console.log('Contract address:', process.env.CONTRACT_ADDRESS); const contract = new ethers.Contract( process.env.CONTRACT_ADDRESS, contractABI, provider ); // Проверяем, что библиотека ethers.js правильно импортирована console.log('Ethers.js version:', ethers.version); // Порядок middleware важен! // 1. CORS должен быть первым app.use(cors({ origin: ['http://127.0.0.1:5173', 'http://localhost:5173'], credentials: true, methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'], exposedHeaders: ['Set-Cookie'] })); // Добавьте после настройки CORS app.use(helmet()); // 2. Затем парсеры app.use(express.json()); app.use(express.urlencoded({ extended: true })); // 3. Затем сессии app.use(session({ secret: process.env.SESSION_SECRET || 'your-secret-key', resave: true, saveUninitialized: true, cookie: { httpOnly: true, secure: false, sameSite: 'lax', maxAge: 24 * 60 * 60 * 1000 }, store: sessionStore })); // Добавьте после настройки сессий app.use((req, res, next) => { // console.log('Middleware для проверки сессии:', { // url: req.url, // method: req.method, // sessionID: req.sessionID, // session: req.session ? { // isAuthenticated: req.session.isAuthenticated, // authenticated: req.session.authenticated, // address: req.session.address, // isAdmin: req.session.isAdmin // } : null, // cookies: req.cookies, // headers: { // cookie: req.headers.cookie // } // }); if (req.session.store) { req.session.store.on('error', (error) => { console.error('Session store error:', error); }); } next(); }); // Добавьте после настройки парсеров app.use((req, res, next) => { // if (req.method === 'POST' && req.headers['content-type'] === 'application/json') { // console.log('POST request body:', { // url: req.url, // body: JSON.stringify(req.body) // }); // } next(); }); const requireAuth = (req, res, next) => { if (!req.session.authenticated || !req.session.address) { return res.status(401).json({ error: 'Unauthorized' }); } next(); }; app.use('/api/protected', requireAuth); // Добавляем middleware для проверки состояния аутентификации app.use((req, res, next) => { // console.log('Auth check middleware:', { // url: req.url, // method: req.method, // sessionID: req.sessionID, // session: req.session ? { // isAuthenticated: req.session.isAuthenticated, // authenticated: req.session.authenticated, // address: req.session.address, // isAdmin: req.session.isAdmin // } : null // }); next(); }); // Добавьте после настройки парсеров app.use((req, res, next) => { // if (req.method === 'POST' && req.headers['content-type'] === 'application/json') { // console.log('POST request body:', { // url: req.url, // body: JSON.stringify(req.body) // }); // } next(); }); // Добавляем middleware для отладки сессий app.use((req, res, next) => { // console.log('Session debug:', { // url: req.url, // method: req.method, // sessionID: req.sessionID, // cookies: req.headers.cookie, // session: req.session ? { // isAuthenticated: req.session.isAuthenticated, // authenticated: req.session.authenticated, // address: req.session.address, // isAdmin: req.session.isAdmin, // nonce: req.session.nonce ? '[REDACTED]' : undefined, // pendingAddress: req.session.pendingAddress // } : null // }); next(); }); // Настройка CSRF-защиты const csrfProtection = csrf({ cookie: { key: '_csrf', path: '/', httpOnly: true, secure: process.env.NODE_ENV === 'production', // true в production, false в development sameSite: process.env.NODE_ENV === 'production' ? 'strict' : 'lax' } }); // Применяем CSRF-защиту только к определенным маршрутам app.use('/api/protected', csrfProtection); app.use('/api/admin', csrfProtection); app.use('/api/kanban', csrfProtection); // Маршрут для получения CSRF-токена app.get('/api/csrf-token', csrfProtection, (req, res) => { res.json({ csrfToken: req.csrfToken() }); }); // Обработчик ошибок CSRF app.use((err, req, res, next) => { if (err.code === 'EBADCSRFTOKEN') { console.error('CSRF error:', { url: req.url, method: req.method, headers: req.headers, body: req.body }); return res.status(403).json({ error: 'CSRF token validation failed', message: 'Your session may have expired. Please refresh the page and try again.' }); } next(err); }); async function initServices() { try { console.log('Инициализация сервисов...'); // Инициализируем ботов, если они нужны if (process.env.TELEGRAM_BOT_TOKEN) { telegramBot = new TelegramBotService(process.env.TELEGRAM_BOT_TOKEN); console.log('Telegram бот инициализирован'); } if (process.env.EMAIL_USER && process.env.EMAIL_PASS) { emailBot = new EmailBotService(process.env.EMAIL_USER, process.env.EMAIL_PASS); console.log('Email бот инициализирован'); } console.log('Все сервисы успешно инициализированы'); } catch (error) { console.error('Ошибка при инициализации сервисов:', error); } } app.use('/api/users', usersRouter); app.use('/api/auth', authRouter); app.use('/api/contracts', contractsRouter); app.use('/api/access', accessRouter); app.use('/api/chat', chatRouter); app.use('/api/debug', debugRouter); app.use('/api/identities', identitiesRouter); app.use('/api/kanban', kanbanRouter); // Добавьте простой эндпоинт для проверки состояния сервера app.get('/api/health', (req, res) => { res.json({ status: 'ok', timestamp: new Date().toISOString() }); }); // Добавьте после настройки маршрутов app.post('/api/verify', async (req, res) => { try { // Перенаправляем запрос на /api/auth/verify const { message, signature } = req.body; console.log("Перенаправление запроса на /api/auth/verify:", { message, signature }); // Проверяем наличие необходимых данных if (!message || !message.address || !signature) { return res.status(400).json({ success: false, error: 'Отсутствуют необходимые данные для верификации' }); } const address = message.address.toLowerCase(); console.log("Адрес из сообщения:", address); // Проверяем, является ли пользователь администратором const isAdmin = true; // Для примера всегда true try { const siweMessage = new SiweMessage(message); const fields = await siweMessage.validate(signature); if (fields.address.toLowerCase() !== address.toLowerCase()) { return res.status(401).json({ success: false, error: 'Invalid signature' }); } // Только после проверки устанавливаем сессию req.session.authenticated = true; req.session.address = fields.address; req.session.lastSignature = signature; // Сохраняем сессию req.session.save(); } catch (error) { return res.status(401).json({ success: false, error: error.message }); } // Сохраняем данные в сессии req.session.isAuthenticated = true; req.session.isAdmin = isAdmin; // Явно сохраняем сессию req.session.save((err) => { if (err) { console.error('Ошибка сохранения сессии:', err); return res.status(500).json({ error: 'Session save error' }); } console.log('Сессия успешно сохранена:', { sessionID: req.sessionID, session: { isAuthenticated: req.session.isAuthenticated, authenticated: req.session.authenticated, address: req.session.address, isAdmin: req.session.isAdmin } }); res.cookie('authToken', 'true', { maxAge: 86400000, httpOnly: false, secure: false, sameSite: 'lax', path: '/' }); res.json({ success: true, address: address, isAdmin: isAdmin }); }); } catch (error) { console.error("Ошибка верификации:", error); res.status(500).json({ success: false, error: error.message || 'Внутренняя ошибка сервера' }); } }); // Добавьте после настройки маршрутов app.get('/api/session', (req, res) => { console.log('Запрос сессии в server.js:', { sessionExists: !!req.session, sessionID: req.sessionID, isAuthenticated: req.session?.isAuthenticated, authenticated: req.session?.authenticated, address: req.session?.address }); if (req.session && (req.session.isAuthenticated || req.session.authenticated)) { res.json({ isAuthenticated: true, authenticated: true, address: req.session.address, isAdmin: req.session.isAdmin }); } else { res.json({ isAuthenticated: false, authenticated: false, address: null, isAdmin: false }); } }); app.get('/api/balance', requireAuth, async (req, res) => { try { const balance = await contract.balanceOf(req.session.address); res.json({ balance: balance.toString() }); } catch (error) { res.status(500).json({ error: error.message }); } }); // Добавляем тестовые маршруты API app.get('/api/public', (req, res) => { res.json({ message: 'This is a public API endpoint' }); }); app.get('/api/protected', (req, res) => { res.json({ message: 'This is a protected API endpoint', user: { address: req.session.address, isAdmin: req.session.isAdmin } }); }); app.get('/api/admin', (req, res) => { res.json({ message: 'This is an admin API endpoint', user: { address: req.session.address, isAdmin: req.session.isAdmin } }); }); // Добавьте обработчик ошибок app.use((err, req, res, next) => { console.error('Глобальный обработчик ошибок:', err); // Обработка ошибок CSRF if (err.code === 'EBADCSRFTOKEN') { return res.status(403).json({ error: 'Недействительный CSRF-токен', message: 'Возможно, ваша сессия истекла. Пожалуйста, обновите страницу и попробуйте снова.' }); } // Обработка ошибок валидации if (err.name === 'ValidationError') { return res.status(400).json({ error: 'Ошибка валидации', details: err.details || err.message }); } // Обработка ошибок базы данных if (err.code === '23505') { // Postgres unique violation return res.status(409).json({ error: 'Конфликт данных', message: 'Запись с такими данными уже существует.' }); } // Общая обработка ошибок res.status(err.status || 500).json({ error: 'Внутренняя ошибка сервера', message: process.env.NODE_ENV === 'production' ? 'Что-то пошло не так' : err.message }); }); // Перед запуском сервера console.log('Перед запуском сервера на порту:', PORT); // Запуск сервера и инициализация сервисов const server = app.listen(PORT, '0.0.0.0', async () => { console.log(`Server is running on port ${PORT}`); console.log('Server address:', server.address()); // Инициализируем сервисы без блокировки запуска сервера initServices().catch(err => { console.error('Ошибка при инициализации сервисов:', err); }); // Проверяем доступность Ollama в фоновом режиме try { const { checkOllamaAvailability } = require('./services/ollama'); checkOllamaAvailability().catch(err => { console.error('Ошибка при проверке Ollama:', err); }); } catch (error) { console.error('Ошибка при импорте модуля Ollama:', error); } }).on('error', (err) => { if (err.code === 'EADDRINUSE') { console.error(`Port ${PORT} is already in use. Please try another port.`); process.exit(1); } else { console.error('Server error:', err); } }); // Добавляем graceful shutdown process.on('SIGTERM', () => { console.log('SIGTERM signal received: closing HTTP server'); server.close(() => { console.log('HTTP server closed'); process.exit(0); }); }); // Проверяем доступность Ollama сервера async function checkOllamaServer() { try { const response = await axios.get('http://localhost:11434/api/tags'); if (response.status === 200) { console.log('Ollama сервер доступен'); // Тестируем прямой запрос к Ollama try { console.log('Тестируем прямой запрос к Ollama...'); const model = new ChatOllama({ baseUrl: 'http://localhost:11434', model: 'llama3', temperature: 0.2, }); const result = await model.invoke('Привет, как дела?'); console.log('Ответ от Ollama:', result); } catch (testError) { console.error('Ошибка при тестировании Ollama:', testError); } // Инициализируем векторное хранилище try { console.log('Инициализируем векторное хранилище...'); const vectorStore = await getVectorStore(); console.log('Векторное хранилище инициализировано'); } catch (vectorError) { console.error('Ошибка при инициализации векторного хранилища:', vectorError); } return true; } return false; } catch (error) { console.error('Ollama сервер недоступен:', error.message); return false; } } // Настройка периодической очистки устаревших сессий const pgSessionCleanup = setInterval(function() { console.log('Cleaning up expired sessions...'); pool.query('DELETE FROM session WHERE expire < NOW()') .then(result => { if (result.rowCount > 0) { console.log(`Removed ${result.rowCount} expired sessions`); } }) .catch(err => console.error('Error cleaning up sessions:', err)); }, 3600000); // Очистка каждый час // Очистка интервала при завершении работы process.on('SIGTERM', () => { clearInterval(pgSessionCleanup); console.log('SIGTERM signal received: closing HTTP server'); server.close(() => { console.log('HTTP server closed'); process.exit(0); }); }); // Функция для создания таблиц async function ensureTablesExist() { try { // Проверяем существование таблицы users const result = await pool.query(` SELECT EXISTS ( SELECT FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'users' ); `); // Если таблица не существует, создаем все таблицы if (!result.rows[0].exists) { console.log('Таблицы не найдены, создаем...'); // SQL-запросы для создания таблиц const createTablesSql = ` -- Таблица пользователей CREATE TABLE IF NOT EXISTS users ( id SERIAL PRIMARY KEY, address VARCHAR(255) UNIQUE, email VARCHAR(255) UNIQUE, telegram_id VARCHAR(255) UNIQUE, username VARCHAR(255), is_admin BOOLEAN DEFAULT FALSE, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); -- Индексы для таблицы пользователей CREATE INDEX IF NOT EXISTS idx_users_address ON users(address); CREATE INDEX IF NOT EXISTS idx_users_email ON users(email); CREATE INDEX IF NOT EXISTS idx_users_telegram_id ON users(telegram_id); -- Таблица сессий CREATE TABLE IF NOT EXISTS session ( sid VARCHAR NOT NULL, sess JSON NOT NULL, expire TIMESTAMP(6) NOT NULL, CONSTRAINT session_pkey PRIMARY KEY (sid) ); -- Индекс для таблицы сессий CREATE INDEX IF NOT EXISTS idx_session_expire ON session(expire); -- Таблица канбан-досок CREATE TABLE IF NOT EXISTS kanban_boards ( id SERIAL PRIMARY KEY, title VARCHAR(255) NOT NULL, description TEXT, owner_id INTEGER REFERENCES users(id), is_public BOOLEAN DEFAULT FALSE, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); -- Таблица колонок канбан-доски CREATE TABLE IF NOT EXISTS kanban_columns ( id SERIAL PRIMARY KEY, board_id INTEGER REFERENCES kanban_boards(id) ON DELETE CASCADE, title VARCHAR(255) NOT NULL, position INTEGER NOT NULL, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); -- Таблица карточек канбан-доски CREATE TABLE IF NOT EXISTS kanban_cards ( id SERIAL PRIMARY KEY, column_id INTEGER REFERENCES kanban_columns(id) ON DELETE CASCADE, title VARCHAR(255) NOT NULL, description TEXT, position INTEGER NOT NULL, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); -- Индексы для таблиц канбан CREATE INDEX IF NOT EXISTS idx_kanban_boards_owner ON kanban_boards(owner_id); CREATE INDEX IF NOT EXISTS idx_kanban_columns_board ON kanban_columns(board_id); CREATE INDEX IF NOT EXISTS idx_kanban_cards_column ON kanban_cards(column_id); -- Таблица сообщений чата CREATE TABLE IF NOT EXISTS chat_messages ( id SERIAL PRIMARY KEY, user_id INTEGER REFERENCES users(id), sender VARCHAR(50) NOT NULL, message TEXT NOT NULL, created_at TIMESTAMP DEFAULT NOW() ); -- Индекс для таблицы сообщений CREATE INDEX IF NOT EXISTS idx_chat_messages_user ON chat_messages(user_id); `; await pool.query(createTablesSql); console.log('Таблицы успешно созданы'); } else { console.log('Таблицы уже существуют'); } } catch (error) { console.error('Ошибка при проверке/создании таблиц:', error); } } // Вызываем функцию при запуске сервера ensureTablesExist(); // Добавляем middleware для проверки аутентификации app.use('/api/protected', (req, res, next) => { // console.log('Protected route middleware:', { // session: req.session, // authenticated: req.session.authenticated, // address: req.session.address // }); if (!req.session.authenticated) { return res.status(401).json({ error: 'Unauthorized' }); } next(); }); // Добавляем middleware для проверки прав администратора app.use('/api/admin', (req, res, next) => { // console.log('Admin route middleware:', { // session: req.session, // authenticated: req.session.authenticated, // isAdmin: req.session.isAdmin // }); if (!req.session.authenticated || !req.session.isAdmin) { return res.status(403).json({ error: 'Forbidden' }); } next(); });