diff --git a/backend/app.js b/backend/app.js index 0bca9a3..4164315 100644 --- a/backend/app.js +++ b/backend/app.js @@ -45,7 +45,7 @@ app.use(session({ app.use(express.json()); app.use(express.urlencoded({ extended: true })); -// Настройка CORS (должна быть после настройки сессий) +// Настройка CORS app.use(cors({ origin: function(origin, callback) { // Разрешаем запросы с localhost и 127.0.0.1 @@ -63,7 +63,9 @@ app.use(cors({ callback(new Error('Not allowed by CORS')); } }, - credentials: true + credentials: true, + methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], + allowedHeaders: ['Content-Type', 'Authorization'] })); // Настройка безопасности @@ -77,6 +79,15 @@ app.use((req, res, next) => { next(); }); +// Добавляем middleware для установки заголовков CORS +app.use((req, res, next) => { + res.header('Access-Control-Allow-Origin', req.headers.origin || '*'); + res.header('Access-Control-Allow-Credentials', 'true'); + res.header('Access-Control-Allow-Methods', 'GET,HEAD,PUT,PATCH,POST,DELETE'); + res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization'); + next(); +}); + // Маршруты API app.use('/api/auth', authRoutes); app.use('/api/access', accessRoutes); diff --git a/backend/db/index.js b/backend/db/index.js new file mode 100644 index 0000000..2adf551 --- /dev/null +++ b/backend/db/index.js @@ -0,0 +1,19 @@ +const createGuestMessagesTable = require('./migrations/create_guest_messages_table'); + +async function initDatabase() { + try { + // ... существующий код ... + + // Выполняем миграции + await pool.query(createUsersTable); + await pool.query(createSessionTable); + await pool.query(createNoncesTable); + await pool.query(createMessagesTable); + await pool.query(createConversationsTable); + await pool.query(createGuestMessagesTable); + + // ... существующий код ... + } catch (error) { + // ... существующий код ... + } +} \ No newline at end of file diff --git a/backend/db/migrations/create_guest_messages_table.js b/backend/db/migrations/create_guest_messages_table.js new file mode 100644 index 0000000..1fa844c --- /dev/null +++ b/backend/db/migrations/create_guest_messages_table.js @@ -0,0 +1,15 @@ +// Создаем таблицу для хранения сообщений неаутентифицированных пользователей +const createGuestMessagesTable = ` +CREATE TABLE IF NOT EXISTS guest_messages ( + id SERIAL PRIMARY KEY, + guest_id VARCHAR(255) NOT NULL, + content TEXT NOT NULL, + language VARCHAR(10) DEFAULT 'en', + is_ai BOOLEAN DEFAULT FALSE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_guest_messages_guest_id ON guest_messages(guest_id); +`; + +module.exports = createGuestMessagesTable; \ No newline at end of file diff --git a/backend/middleware/auth.js b/backend/middleware/auth.js index e84817a..3cba7f7 100644 --- a/backend/middleware/auth.js +++ b/backend/middleware/auth.js @@ -1,4 +1,4 @@ -const { createError } = require('./errorHandler'); +const { createError } = require('../utils/error'); const authService = require('../services/auth-service'); const logger = require('../utils/logger'); const { USER_ROLES } = require('../utils/constants'); @@ -7,13 +7,57 @@ const db = require('../db'); /** * Middleware для проверки аутентификации */ -function requireAuth(req, res, next) { - console.log('Session in requireAuth:', req.session); - if (!req.session || !req.session.authenticated) { - return next(createError(401, 'Требуется аутентификация')); +const requireAuth = async (req, res, next) => { + try { + console.log('Session in requireAuth:', req.session); + console.log('Cookies received:', req.headers.cookie); + console.log('Authorization header:', req.headers.authorization); + + // Проверяем, что пользователь аутентифицирован через сессию + if (req.session && req.session.authenticated) { + return next(); + } + + // Проверяем заголовок авторизации + const authHeader = req.headers.authorization; + if (authHeader && authHeader.startsWith('Bearer ')) { + const address = authHeader.split(' ')[1]; + console.log('Found address in Authorization header:', address); + + try { + // Находим пользователя по адресу + const { pool } = require('../db'); + console.log('Querying database for user with address:', address); + const result = await pool.query('SELECT * FROM users WHERE LOWER(address) = LOWER($1)', [address]); + console.log('Database query result:', result.rows); + + if (result.rows.length > 0) { + const user = result.rows[0]; + console.log('Found user by address:', user); + + // Устанавливаем данные пользователя в запросе + req.user = { + userId: user.id, + address: address, + isAdmin: user.is_admin + }; + + return next(); + } else { + console.log('No user found with address:', address); + } + } catch (error) { + console.error('Error finding user by address:', error); + } + } + + // Если пользователь не аутентифицирован, возвращаем ошибку + return res.status(401).json({ error: 'Unauthorized' }); + } catch (error) { + console.error('Unexpected error in requireAuth middleware:', error); + return res.status(500).json({ error: 'Internal server error' }); } - next(); -} +}; /** * Middleware для проверки прав администратора diff --git a/backend/routes/auth.js b/backend/routes/auth.js index 0865cb5..ddd26ca 100644 --- a/backend/routes/auth.js +++ b/backend/routes/auth.js @@ -10,6 +10,7 @@ const { checkRole, requireAuth } = require('../middleware/auth'); const { pool } = require('../db'); const { verifySignature, checkAccess, findOrCreateUser } = require('../utils/auth'); const authService = require('../services/auth-service'); +const { SiweMessage } = require('siwe'); // Создайте лимитер для попыток аутентификации const authLimiter = rateLimit({ @@ -31,20 +32,19 @@ router.get('/nonce', async (req, res) => { // Генерируем случайный nonce const nonce = crypto.randomBytes(16).toString('hex'); - // Сохраняем nonce в сессии - req.session.authNonce = nonce; - req.session.pendingAddress = address; + // Удаляем старые nonce для этого адреса + await db.query(` + DELETE FROM nonces + WHERE identity_value = $1 + `, [address.toLowerCase()]); - // Важно: сохраняем сессию перед отправкой ответа - await new Promise((resolve, reject) => { - req.session.save(err => { - if (err) reject(err); - else resolve(); - }); - }); + // Сохраняем новый nonce + await db.query(` + INSERT INTO nonces (identity_value, nonce, expires_at) + VALUES ($1, $2, NOW() + INTERVAL '15 minutes') + `, [address.toLowerCase(), nonce]); - console.log('Сессия после генерации nonce:', req.session); - console.log('Сессия после сохранения:', req.session); + console.log(`Nonce ${nonce} сохранен для адреса ${address}`); return res.json({ nonce }); } catch (error) { @@ -87,39 +87,48 @@ router.post('/verify', async (req, res) => { 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' }); + // Получаем nonce из базы данных + const nonceResult = await db.query(` + SELECT nonce FROM nonces + WHERE identity_value = $1 AND expires_at > NOW() AND used = false + `, [address.toLowerCase()]); + + if (nonceResult.rows.length === 0) { + console.error(`No valid nonce found for address ${address}`); + return res.status(401).json({ error: 'Invalid or expired nonce' }); } + const nonce = nonceResult.rows[0].nonce; + console.log(`Found nonce ${nonce} for address ${address}`); + // Проверяем подпись - const isValid = await verifySignature(req.session.authNonce, signature, address); + const isValid = await verifySignature(nonce, signature, address); console.log('Signature verification result:', isValid); if (!isValid) { return res.status(401).json({ error: 'Invalid signature' }); } + // Помечаем nonce как использованный + await db.query(` + UPDATE nonces + SET used = true + WHERE identity_value = $1 + `, [address.toLowerCase()]); + // Находим или создаем пользователя 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) { @@ -132,7 +141,11 @@ router.post('/verify', async (req, res) => { }); }); + // Добавляем задержку для гарантии сохранения сессии (временное решение) + await new Promise(resolve => setTimeout(resolve, 100)); + console.log('Authentication successful for user:', { userId, address, isAdmin }); + console.log('Session after save:', req.session); return res.json({ authenticated: true, @@ -305,56 +318,53 @@ router.post('/link-identity', async (req, res) => { } }); -// Проверка текущей сессии +// Проверка аутентификации 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 - }); + try { + console.log('Сессия при проверке:', req.session); + + if (req.session && req.session.authenticated) { + return res.json({ + authenticated: true, + userId: req.session.userId, + address: req.session.address, + isAdmin: req.session.isAdmin, + authType: req.session.authType || 'wallet' + }); + } else { + return res.json({ authenticated: false }); + } + } catch (error) { + console.error('Ошибка при проверке аутентификации:', error); + return res.status(500).json({ error: 'Internal server error' }); } }); -// Обработчик выхода из системы -router.post('/logout', (req, res) => { +// Выход из системы +router.post('/logout', async (req, res) => { try { - // Сохраняем sessionID перед удалением сессии + // Сохраняем ID сессии до уничтожения 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 }); - }); + // Уничтожаем сессию + req.session.destroy(); + + // Удаляем сессию из базы данных + try { + const { pool } = require('../db'); + await pool.query('DELETE FROM session WHERE sid = $1', [sessionID]); + console.log(`Сессия ${sessionID} удалена из базы данных`); + } catch (dbError) { + console.error('Ошибка при удалении сессии из базы данных:', dbError); + } + + // Очищаем куки + res.clearCookie('connect.sid'); + + res.json({ success: true }); } catch (error) { - console.error('Logout error:', error); - res.status(500).json({ error: 'Internal server error' }); + console.error('Ошибка при выходе из системы:', error); + res.status(500).json({ error: 'Ошибка сервера' }); } }); @@ -502,42 +512,72 @@ router.get('/check-access', requireAuth, (req, res) => { } }); -// Добавьте этот маршрут в 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' }); + if (req.session && req.session.authenticated) { + console.log('Обновление сессии для пользователя:', req.session.userId); + + // Обновляем время жизни сессии + req.session.cookie.maxAge = 30 * 24 * 60 * 60 * 1000; // 30 дней + + // Сохраняем обновленную сессию + await new Promise((resolve, reject) => { + req.session.save(err => { + if (err) { + console.error('Ошибка при сохранении сессии:', err); + reject(err); + } else { + console.log('Сессия успешно обновлена'); + resolve(); + } + }); + }); + + return res.json({ success: true }); + } else if (address) { + // Если сессия не аутентифицирована, но есть адрес + try { + const { pool } = require('../db'); + const result = await pool.query('SELECT * FROM users WHERE address = $1', [address]); + + if (result.rows.length > 0) { + const user = result.rows[0]; + + // Обновляем сессию + req.session.authenticated = true; + req.session.userId = user.id; + req.session.address = address; + req.session.isAdmin = user.is_admin; + req.session.authType = 'wallet'; + + // Сохраняем обновленную сессию + await new Promise((resolve, reject) => { + req.session.save(err => { + if (err) { + console.error('Ошибка при сохранении сессии:', err); + reject(err); + } else { + console.log('Сессия успешно обновлена'); + resolve(); + } + }); + }); + + return res.json({ success: true }); + } + } catch (error) { + console.error('Ошибка при проверке пользователя:', error); + } } - 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' - }); + // Если не удалось обновить сессию, возвращаем успех=false, но не ошибку + return res.json({ success: false }); } catch (error) { - logger.error(`Error refreshing session: ${error.message}`); - res.status(500).json({ error: 'Internal server error' }); + console.error('Ошибка при обновлении сессии:', error); + res.status(500).json({ error: 'Ошибка сервера' }); } }); @@ -575,7 +615,7 @@ router.post('/update-admin-status', async (req, res) => { ]); console.log( - `Обновлен статус администратора для пользователя с адресом ${address} на ${isAdmin}` + `Создан новый пользователь с адресом ${address} и статусом администратора ${isAdmin}` ); } @@ -586,75 +626,4 @@ router.post('/update-admin-status', async (req, res) => { } }); -// Маршрут для проверки структуры таблицы 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; +module.exports = router; \ No newline at end of file diff --git a/backend/routes/chat.js b/backend/routes/chat.js index a8784d2..66b0ef5 100644 --- a/backend/routes/chat.js +++ b/backend/routes/chat.js @@ -5,6 +5,66 @@ const { getVectorStore } = require('../services/vectorStore'); const db = require('../db'); const { requireAuth, requireAdmin } = require('../middleware/auth'); const logger = require('../utils/logger'); +const crypto = require('crypto'); + +// Добавьте эту функцию в начало файла chat.js +async function getAIResponse(message, language = 'ru') { + // Определяем язык сообщения, если не указан явно + let detectedLanguage = language; + if (!language || language === 'auto') { + // Простая эвристика для определения языка + const cyrillicPattern = /[а-яА-ЯёЁ]/; + detectedLanguage = cyrillicPattern.test(message) ? 'ru' : 'en'; + } + + // Формируем системный промпт в зависимости от языка + let systemPrompt = ''; + if (detectedLanguage === 'ru') { + systemPrompt = 'Вы - полезный ассистент. Отвечайте на русском языке.'; + } else { + systemPrompt = 'You are a helpful assistant. Respond in English.'; + } + + // Создаем экземпляр ChatOllama + const chat = new ChatOllama({ + baseUrl: process.env.OLLAMA_BASE_URL || 'http://localhost:11434', + model: process.env.OLLAMA_MODEL || 'mistral', + system: systemPrompt + }); + + console.log('Отправка запроса к Ollama...'); + + // Получаем ответ от модели + try { + const response = await chat.invoke(message); + return response.content; + } catch (error) { + console.error('Ошибка при вызове ChatOllama:', error); + + // Альтернативный метод запроса через прямой API + try { + console.log('Пробуем альтернативный метод запроса...'); + const response = await fetch(`${process.env.OLLAMA_BASE_URL || 'http://localhost:11434'}/api/generate`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + model: process.env.OLLAMA_MODEL || 'mistral', + prompt: message, + system: systemPrompt, + stream: false + }), + }); + + const data = await response.json(); + return data.response; + } catch (fallbackError) { + console.error('Ошибка при использовании альтернативного метода:', fallbackError); + return "Извините, я не смог обработать ваш запрос. Пожалуйста, попробуйте позже."; + } + } +} // Обработчик сообщений чата router.post('/message', requireAuth, async (req, res) => { @@ -13,8 +73,16 @@ router.post('/message', requireAuth, async (req, res) => { try { const { message, language = 'ru' } = req.body; + const userId = typeof req.session.userId === 'object' + ? req.session.userId.userId + : req.session.userId; - console.log(`Получено сообщение: ${message}, язык: ${language}`); + console.log(`Получено сообщение: ${message}, язык: ${language}, userId: ${userId}`); + + // Проверяем, что userId существует + if (!userId) { + return res.status(400).json({ error: 'User ID is required' }); + } // Определяем язык сообщения, если не указан явно let detectedLanguage = language; @@ -92,14 +160,59 @@ router.post('/message', requireAuth, async (req, res) => { } } - // Отправляем ответ клиенту + // Получаем или создаем диалог + let conversationId; + const conversationResult = await db.query(` + SELECT id FROM conversations + WHERE user_id = $1 + ORDER BY updated_at DESC + LIMIT 1 + `, [userId]); + + if (conversationResult.rows.length === 0) { + // Создаем новый диалог + const newConversationResult = await db.query(` + INSERT INTO conversations (user_id, created_at, updated_at) + VALUES ($1, NOW(), NOW()) + RETURNING id + `, [userId]); + conversationId = newConversationResult.rows[0].id; + console.log('Created new conversation:', conversationId); + } else { + conversationId = conversationResult.rows[0].id; + console.log('Using existing conversation:', conversationId); + } + + // Сохраняем сообщение пользователя + const userMessageResult = await db.query(` + INSERT INTO messages (conversation_id, sender_type, sender_id, content, channel, created_at) + VALUES ($1, 'user', $2, $3, 'web', NOW()) + RETURNING id + `, [conversationId, userId, message]); + console.log('Saved user message:', userMessageResult.rows[0].id); + + // Сохраняем ответ ИИ + const aiMessageResult = await db.query(` + INSERT INTO messages (conversation_id, sender_type, content, channel, created_at) + VALUES ($1, 'ai', $2, 'web', NOW()) + RETURNING id + `, [conversationId, aiResponse]); + console.log('Saved AI message:', aiMessageResult.rows[0].id); + + // Обновляем время последнего сообщения в диалоге + await db.query(` + UPDATE conversations + SET updated_at = NOW() + WHERE id = $1 + `, [conversationId]); + res.json({ reply: aiResponse, language: detectedLanguage }); } catch (error) { - logger.error('Error processing message:', error); - res.status(500).json({ error: 'Внутренняя ошибка сервера' }); + console.error('Error processing message:', error); + res.status(500).json({ error: 'Internal server error' }); } }); @@ -119,24 +232,83 @@ router.get('/models', async (req, res) => { } }); -// Маршрут для получения истории диалогов (доступен пользователю для своих диалогов) +// Получение истории сообщений router.get('/history', requireAuth, async (req, res) => { try { - const userId = req.session.userId; - const { limit = 50, offset = 0 } = req.query; + // Получаем ID пользователя из сессии или из объекта пользователя + const userId = req.session?.userId || req.user?.userId; - const result = await db.query(` - SELECT id, channel, sender_type, content, metadata, created_at - FROM chat_history - WHERE user_id = $1 - ORDER BY created_at DESC - LIMIT $2 OFFSET $3 - `, [userId, limit, offset]); + console.log('Запрос истории чата для пользователя:', userId); + console.log('User object from request:', req.user); - res.json(result.rows); + // Проверяем, что userId существует + if (!userId) { + console.error('Пользователь не аутентифицирован'); + return res.status(401).json({ error: 'Unauthorized' }); + } + + // Получаем историю сообщений из базы данных + console.log('Querying chat history for user:', userId); + + // Проверяем, существует ли таблица messages + try { + const tableCheck = await db.query(` + SELECT EXISTS ( + SELECT FROM information_schema.tables + WHERE table_name = 'messages' + ); + `); + + console.log('Table messages exists:', tableCheck.rows[0].exists); + + if (tableCheck.rows[0].exists) { + // Используем таблицу messages + const result = await db.query(` + SELECT m.*, c.user_id + FROM messages m + JOIN conversations c ON m.conversation_id = c.id + WHERE c.user_id = $1 + ORDER BY m.created_at ASC + `, [userId]); + + console.log(`Найдено ${result.rows.length} сообщений для пользователя ${userId}`); + + return res.json({ messages: result.rows }); + } else { + // Проверяем, существует ли таблица chat_history + const chatHistoryCheck = await db.query(` + SELECT EXISTS ( + SELECT FROM information_schema.tables + WHERE table_name = 'chat_history' + ); + `); + + console.log('Table chat_history exists:', chatHistoryCheck.rows[0].exists); + + if (chatHistoryCheck.rows[0].exists) { + // Используем таблицу chat_history + const result = await db.query(` + SELECT * FROM chat_history + WHERE user_id = $1 + ORDER BY created_at ASC + `, [userId]); + + console.log(`Найдено ${result.rows.length} сообщений для пользователя ${userId}`); + + return res.json({ messages: result.rows }); + } else { + // Ни одна из таблиц не существует + console.log('No message tables found in database'); + return res.json({ messages: [] }); + } + } + } catch (error) { + console.error('Error checking tables:', error); + return res.json({ messages: [] }); + } } catch (error) { - logger.error('Error fetching chat history:', error); - res.status(500).json({ error: 'Внутренняя ошибка сервера' }); + console.error('Error fetching chat history:', error); + res.status(500).json({ error: 'Internal server error' }); } }); @@ -173,4 +345,72 @@ router.get('/admin/history', requireAdmin, async (req, res) => { } }); +// Обработчик для гостевых сообщений +router.post('/guest-message', async (req, res) => { + try { + const { message, language } = req.body; + console.log(`Получено гостевое сообщение: ${message} язык: ${language}`); + + // Генерируем временный ID сессии, если его нет + if (!req.session.guestId) { + req.session.guestId = crypto.randomBytes(16).toString('hex'); + } + + // Сохраняем сообщение в базе данных с временным ID + await db.query(` + INSERT INTO guest_messages (guest_id, content, language, created_at) + VALUES ($1, $2, $3, NOW()) + `, [req.session.guestId, message, language]); + + // Отправляем запрос к AI + const aiResponse = await getAIResponse(message, language); + + // Сохраняем ответ AI в базе данных + await db.query(` + INSERT INTO guest_messages (guest_id, content, language, created_at, is_ai) + VALUES ($1, $2, $3, NOW(), true) + `, [req.session.guestId, aiResponse, language]); + + return res.json({ message: aiResponse, reply: aiResponse }); + } catch (error) { + console.error('Error processing guest message:', error); + return res.status(500).json({ error: 'Internal server error' }); + } +}); + +// Обработчик для связывания гостевых сообщений с пользователем +router.post('/link-guest-messages', requireAuth, async (req, res) => { + try { + const userId = req.session.userId; + const guestId = req.session.guestId; + + if (!guestId) { + return res.json({ success: true, message: 'No guest messages to link' }); + } + + // Связываем гостевые сообщения с пользователем + await db.query(` + INSERT INTO messages (user_id, content, role, created_at) + SELECT $1, content, CASE WHEN is_ai THEN 'assistant' ELSE 'user' END, created_at + FROM guest_messages + WHERE guest_id = $2 + ORDER BY created_at + `, [userId, guestId]); + + // Удаляем гостевые сообщения + await db.query(` + DELETE FROM guest_messages + WHERE guest_id = $1 + `, [guestId]); + + // Удаляем временный ID из сессии + delete req.session.guestId; + + return res.json({ success: true }); + } catch (error) { + console.error('Error linking guest messages:', error); + return res.status(500).json({ error: 'Internal server error' }); + } +}); + module.exports = router; diff --git a/backend/server.js b/backend/server.js index 19bb8cf..b36a1e0 100644 --- a/backend/server.js +++ b/backend/server.js @@ -63,10 +63,10 @@ console.log('Ethers.js version:', ethers.version); // 1. CORS должен быть первым app.use( cors({ - origin: ['http://127.0.0.1:5173', 'http://localhost:5173'], - credentials: true, + origin: ['http://localhost:5173', 'http://127.0.0.1:5173'], // Укажем точные домены + credentials: true, // Важно для передачи куки methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], - allowedHeaders: ['Content-Type', 'Authorization', 'X-Auth-Nonce'], + allowedHeaders: ['Content-Type', 'Authorization'] }) ); @@ -173,7 +173,25 @@ app.use((req, res, next) => { // }); // Использовать импортированный middleware для сессий -app.use(sessionMiddleware); +// app.use(sessionMiddleware); + +// Настройка сессий +app.use(session({ + store: new pgSession({ + pool: pool, + tableName: 'session' + }), + secret: process.env.SESSION_SECRET || 'your-secret-key', + resave: true, + saveUninitialized: true, + cookie: { + httpOnly: true, + secure: false, + sameSite: 'lax', + maxAge: 30 * 24 * 60 * 60 * 1000, + path: '/' + } +})); async function initServices() { try { @@ -687,7 +705,7 @@ const cleanupInterval = 24 * 60 * 60 * 1000; // 24 часа setInterval(async () => { try { const { pool } = require('./db'); - const result = await pool.query('DELETE FROM sessions WHERE expire < NOW()'); + const result = await pool.query('DELETE FROM session WHERE expire < NOW()'); console.log(`Очищено ${result.rowCount} устаревших сессий`); } catch (err) { console.error('Ошибка при очистке сессий:', err); @@ -698,7 +716,7 @@ setInterval(async () => { setTimeout(async () => { try { const { pool } = require('./db'); - const result = await pool.query('DELETE FROM sessions WHERE expire < NOW()'); + const result = await pool.query('DELETE FROM session WHERE expire < NOW()'); console.log(`Первоначальная очистка: удалено ${result.rowCount} устаревших сессий`); } catch (err) { console.error('Ошибка при первоначальной очистке сессий:', err); @@ -712,3 +730,14 @@ app.get('/session-debug', (req, res) => { app.get('/check-sessions', async (req, res) => { // Implementation of the endpoint }); + +// Функция для очистки старых сессий +async function cleanupSessions() { + try { + // Удаляем сессии старше 30 дней + const result = await pool.query('DELETE FROM session WHERE expire < NOW() - INTERVAL \'30 days\''); + console.log(`Очищено ${result.rowCount} старых сессий`); + } catch (error) { + console.error('Ошибка при очистке старых сессий:', error); + } +} diff --git a/backend/utils/auth.js b/backend/utils/auth.js index 1f11595..b3e8b83 100644 --- a/backend/utils/auth.js +++ b/backend/utils/auth.js @@ -9,7 +9,7 @@ const provider = new ethers.JsonRpcProvider(process.env.RPC_URL_ETH); /** * Проверяет подпись сообщения - * @param {string} nonce - Независимый идентификатор + * @param {string} nonce - Nonce для проверки * @param {string} signature - Подпись * @param {string} address - Адрес кошелька * @returns {Promise} - Результат проверки @@ -17,12 +17,12 @@ const provider = new ethers.JsonRpcProvider(process.env.RPC_URL_ETH); async function verifySignature(nonce, signature, address) { try { // Создаем сообщение для проверки - const message = `Sign this message to authenticate with our app: ${nonce}`; + const message = `Подпишите это сообщение для аутентификации в DApp for Business. Nonce: ${nonce}`; // Восстанавливаем адрес из подписи const recoveredAddress = ethers.verifyMessage(message, signature); - // Проверяем, что восстановленный адрес совпадает с предоставленным + // Сравниваем адреса (приводим к нижнему регистру для надежности) return recoveredAddress.toLowerCase() === address.toLowerCase(); } catch (error) { console.error('Error verifying signature:', error); @@ -80,66 +80,57 @@ async function checkAccess(walletAddress) { */ async function findOrCreateUser(address) { try { - // Проверяем, существует ли пользователь - const userResult = await db.query(` - SELECT u.id FROM users u - JOIN user_identities ui ON u.id = ui.user_id - WHERE ui.identity_type = 'wallet' AND LOWER(ui.identity_value) = LOWER($1) - `, [address]); - + if (!address) { + throw new Error('Address is required'); + } + + const normalizedAddress = address.toLowerCase(); + + // Сначала проверяем в таблице users + const userResult = await db.query( + 'SELECT id FROM users WHERE LOWER(address) = $1', + [normalizedAddress] + ); + let userId; let isAdmin = false; - + if (userResult.rows.length === 0) { // Если пользователь не найден, создаем его - // Получаем ID роли 'user' const roleResult = await db.query('SELECT id FROM roles WHERE name = $1', ['user']); if (roleResult.rows.length === 0) { throw new Error('Role "user" not found'); } const roleId = roleResult.rows[0].id; - - // Создаем пользователя с ролью 'user' + + // Создаем пользователя const newUserResult = await db.query( - 'INSERT INTO users (role_id, created_at) VALUES ($1, NOW()) RETURNING id', - [roleId] + 'INSERT INTO users (address, role_id, created_at) VALUES ($1, $2, NOW()) RETURNING id', + [normalizedAddress, roleId] ); - + userId = newUserResult.rows[0].id; - + // Добавляем идентификатор кошелька await db.query( 'INSERT INTO user_identities (user_id, identity_type, identity_value, created_at) VALUES ($1, $2, $3, NOW())', - [userId, 'wallet', address.toLowerCase()] + [userId, 'wallet', normalizedAddress] ); - - // Проверяем, является ли пользователь администратором - isAdmin = await checkUserRole(address); - - // Если пользователь администратор, обновляем его роль - if (isAdmin) { - const adminRoleResult = await db.query('SELECT id FROM roles WHERE name = $1', ['admin']); - if (adminRoleResult.rows.length > 0) { - const adminRoleId = adminRoleResult.rows[0].id; - await db.query('UPDATE users SET role_id = $1 WHERE id = $2', [adminRoleId, userId]); - } - } } else { - // Если пользователь найден, получаем его ID userId = userResult.rows[0].id; - - // Проверяем, является ли пользователь администратором - isAdmin = await checkUserRole(address); - - // Обновляем роль пользователя - const roleNameToSet = isAdmin ? 'admin' : 'user'; - const roleToSetResult = await db.query('SELECT id FROM roles WHERE name = $1', [roleNameToSet]); - if (roleToSetResult.rows.length > 0) { - const roleIdToSet = roleToSetResult.rows[0].id; - await db.query('UPDATE users SET role_id = $1 WHERE id = $2', [roleIdToSet, userId]); - } } - + + // Проверяем, является ли пользователь администратором + isAdmin = await checkUserRole(normalizedAddress); + + // Обновляем роль пользователя + const roleNameToSet = isAdmin ? 'admin' : 'user'; + const roleToSetResult = await db.query('SELECT id FROM roles WHERE name = $1', [roleNameToSet]); + if (roleToSetResult.rows.length > 0) { + const roleIdToSet = roleToSetResult.rows[0].id; + await db.query('UPDATE users SET role_id = $1 WHERE id = $2', [roleIdToSet, userId]); + } + return { userId, isAdmin }; } catch (error) { console.error('Error finding or creating user:', error); diff --git a/backend/utils/error.js b/backend/utils/error.js new file mode 100644 index 0000000..c170f54 --- /dev/null +++ b/backend/utils/error.js @@ -0,0 +1,13 @@ +/** + * Создает объект ошибки с указанным сообщением и кодом статуса + * @param {string} message - Сообщение об ошибке + * @param {number} statusCode - HTTP-код статуса (по умолчанию 500) + * @returns {Error} Объект ошибки с дополнительными свойствами + */ +function createError(message, statusCode = 500) { + const error = new Error(message); + error.statusCode = statusCode; + return error; +} + +module.exports = { createError }; \ No newline at end of file diff --git a/frontend/public/index.html b/frontend/public/index.html new file mode 100644 index 0000000..226d2cc --- /dev/null +++ b/frontend/public/index.html @@ -0,0 +1,13 @@ + + + + + + + DApp for Business + + +
+ + + \ No newline at end of file diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 0faf8e5..5a66d95 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -1,111 +1,41 @@ diff --git a/frontend/src/api/axios.js b/frontend/src/api/axios.js index 0d37665..127807d 100644 --- a/frontend/src/api/axios.js +++ b/frontend/src/api/axios.js @@ -1,26 +1,28 @@ import axios from 'axios'; // Создаем экземпляр axios с базовым URL -const api = axios.create({ - baseURL: 'http://localhost:8000', - withCredentials: true, // Важно для передачи куков между запросами +const instance = axios.create({ + baseURL: '/', + withCredentials: true, headers: { 'Content-Type': 'application/json', }, }); -// Удаляем перехватчик, который добавлял заголовок Authorization из localStorage -// api.interceptors.request.use( -// (config) => { -// const address = localStorage.getItem('walletAddress'); -// if (address) { -// config.headers.Authorization = `Bearer ${address}`; -// } -// return config; -// }, -// (error) => { -// return Promise.reject(error); -// } -// ); +// Добавляем перехватчик для добавления заголовка авторизации +instance.interceptors.request.use( + (config) => { + console.log('Axios interceptor running'); + const address = localStorage.getItem('walletAddress'); + if (address) { + console.log('Adding Authorization header in interceptor:', `Bearer ${address}`); + config.headers.Authorization = `Bearer ${address}`; + } + return config; + }, + (error) => { + return Promise.reject(error); + } +); -export default api; \ No newline at end of file +export default instance; \ No newline at end of file diff --git a/frontend/src/components/LinkedAccounts.vue b/frontend/src/components/LinkedAccounts.vue deleted file mode 100644 index 0ee16a4..0000000 --- a/frontend/src/components/LinkedAccounts.vue +++ /dev/null @@ -1,198 +0,0 @@ - - - - - diff --git a/frontend/src/components/Modal.vue b/frontend/src/components/Modal.vue deleted file mode 100644 index 4edb04a..0000000 --- a/frontend/src/components/Modal.vue +++ /dev/null @@ -1,98 +0,0 @@ - - - - - diff --git a/frontend/src/components/Navigation.vue b/frontend/src/components/Navigation.vue deleted file mode 100644 index d187e62..0000000 --- a/frontend/src/components/Navigation.vue +++ /dev/null @@ -1,154 +0,0 @@ - - - - - diff --git a/frontend/src/components/RoleManager.vue b/frontend/src/components/RoleManager.vue deleted file mode 100644 index b4c32b5..0000000 --- a/frontend/src/components/RoleManager.vue +++ /dev/null @@ -1,161 +0,0 @@ - - - - - diff --git a/frontend/src/components/WalletConnection.vue b/frontend/src/components/WalletConnection.vue index f1c9154..cf97171 100644 --- a/frontend/src/components/WalletConnection.vue +++ b/frontend/src/components/WalletConnection.vue @@ -4,64 +4,116 @@ {{ error }} - +
+ +
+
+ {{ formatAddress(authStore.user?.address) }} + +
-