Описание изменений

This commit is contained in:
2025-03-20 18:44:32 +03:00
parent fe9783e814
commit ee6e71d7ec
11 changed files with 1177 additions and 657 deletions

View File

@@ -47,6 +47,7 @@
"pg": "^8.10.0",
"session-file-store": "^1.5.0",
"siwe": "^2.1.4",
"telegraf": "^4.16.3",
"winston": "^3.17.0"
},
"devDependencies": {

View File

@@ -13,6 +13,9 @@ const { sendEmail } = require('../services/emailBot');
const { verificationCodes } = require('../services/telegramBot');
const { checkTokensAndUpdateRole } = require('../services/auth-service');
const { ethers } = require('ethers');
const { initTelegramAuth } = require('../services/telegramBot');
const { initEmailAuth, verifyEmailCode } = require('../services/emailBot');
const { getBot } = require('../services/telegramBot');
// Создайте лимитер для попыток аутентификации
const authLimiter = rateLimit({
@@ -34,24 +37,32 @@ router.get('/nonce', async (req, res) => {
// Генерируем случайный nonce
const nonce = crypto.randomBytes(16).toString('hex');
// Удаляем старые nonce для этого адреса
await db.query(`
DELETE FROM nonces
WHERE identity_value = $1
`, [address.toLowerCase()]);
// Проверяем, существует ли уже nonce для этого адреса
const existingNonce = await db.query(
'SELECT id FROM nonces WHERE identity_value = $1',
[address.toLowerCase()]
);
// Сохраняем новый nonce
await db.query(`
INSERT INTO nonces (identity_value, nonce, expires_at)
VALUES ($1, $2, NOW() + INTERVAL '15 minutes')
`, [address.toLowerCase(), nonce]);
if (existingNonce.rows.length > 0) {
// Обновляем существующий nonce
await db.query(
'UPDATE nonces SET nonce = $1, expires_at = NOW() + INTERVAL \'15 minutes\' WHERE identity_value = $2',
[nonce, address.toLowerCase()]
);
} else {
// Создаем новый 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 ${nonce} сохранен для адреса ${address}`);
return res.json({ nonce });
res.json({ nonce });
} catch (error) {
console.error('Error generating nonce:', error);
return res.status(500).json({ error: 'Internal server error' });
res.status(500).json({ error: 'Failed to generate nonce' });
}
});
@@ -60,77 +71,85 @@ const ERC20_ABI = [
"function balanceOf(address owner) view returns (uint256)"
];
// Проверка подписи и аутентификация
// Верификация подписи и создание сессии
router.post('/verify', async (req, res) => {
try {
const { address, signature, message } = req.body;
console.log('Received verification request:', { address, message });
console.log('Signature:', signature);
// Проверяем подпись через SIWE
const siwe = new SiweMessage(message);
console.log('Created SIWE message object');
const fields = await siwe.verify({ signature });
console.log('SIWE validation result:', fields);
const { address, message, signature } = req.body;
if (!fields || !fields.success) {
console.log('SIWE validation failed');
return res.status(401).json({ error: 'Invalid signature' });
// Проверяем подпись
const isValid = await authService.verifySignature(message, signature, address);
if (!isValid) {
return res.status(401).json({ success: false, error: 'Invalid signature' });
}
// Проверяем nonce
const nonceResult = await db.query(
`SELECT nonce FROM nonces
WHERE identity_value = $1
AND expires_at > NOW()`,
[address.toLowerCase()]
);
console.log('Nonce check result:', nonceResult.rows);
if (!nonceResult.rows.length) {
console.log('Invalid or expired nonce');
return res.status(401).json({ error: 'Invalid or expired nonce' });
const nonceResult = await db.query('SELECT nonce FROM nonces WHERE identity_value = $1', [address.toLowerCase()]);
if (nonceResult.rows.length === 0 || nonceResult.rows[0].nonce !== message.match(/Nonce: ([^\n]+)/)[1]) {
return res.status(401).json({ success: false, error: 'Invalid nonce' });
}
// Проверяем соответствие nonce
if (nonceResult.rows[0].nonce !== fields.data.nonce) {
console.log('Nonce mismatch');
return res.status(401).json({ error: 'Invalid nonce' });
// Находим или создаем пользователя
let userId, isAdmin;
// Ищем пользователя по адресу в таблице user_identities
const userResult = await db.query(`
SELECT u.* FROM users u
JOIN user_identities ui ON u.id = ui.user_id
WHERE ui.provider = 'wallet' AND ui.provider_id = $1
`, [address.toLowerCase()]);
if (userResult.rows.length > 0) {
// Пользователь найден
userId = userResult.rows[0].id;
isAdmin = userResult.rows[0].role === 'admin';
const address = userResult.rows[0].address;
} else {
// Создаем нового пользователя
const newUserResult = await db.query(
'INSERT INTO users (role) VALUES ($1) RETURNING id',
['user']
);
userId = newUserResult.rows[0].id;
isAdmin = false;
// Добавляем идентификатор кошелька
await db.query(
'INSERT INTO user_identities (user_id, provider, provider_id) VALUES ($1, $2, $3)',
[userId, 'wallet', address.toLowerCase()]
);
}
// Получаем или создаем пользователя
const userResult = await db.query(
`INSERT INTO users (address, created_at, updated_at)
VALUES ($1, NOW(), NOW())
ON CONFLICT (address) DO UPDATE
SET updated_at = NOW()
RETURNING id, role = 'admin' as is_admin`,
[address]
);
const userId = userResult.rows[0].id;
const isAdmin = false; // Будет обновлено в createSession
// Используем централизованный сервис
await authService.createSession(req, {
// Обновляем сессию
req.session.userId = userId;
req.session.authenticated = true;
req.session.authType = 'wallet';
req.session.isAdmin = isAdmin;
req.session.address = address.toLowerCase();
// Сохраняем сессию
await new Promise((resolve, reject) => {
req.session.save((err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
// Возвращаем успешный ответ
return res.json({
success: true,
userId,
address,
isAdmin,
authType: 'wallet',
guestId: req.session.guestId
});
res.json({
authenticated: true,
userId,
address,
isAdmin,
authType: 'wallet'
authenticated: true
});
} catch (error) {
console.error('Error:', error);
res.status(500).json({ error: 'Internal server error' });
console.error('Error in /verify:', error);
res.status(500).json({ success: false, error: 'Server error' });
}
});
@@ -488,54 +507,106 @@ router.post('/link-identity', async (req, res) => {
}
});
// Проверка аутентификации
router.get('/check', (req, res) => {
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'
});
// Проверка статуса аутентификации
router.get('/check', async (req, res) => {
console.log('Сессия при проверке:', req.session);
let telegramId = null;
if (req.session.userId && req.session.authType === 'telegram') {
// Проверяем, есть ли telegramId в сессии
if (req.session.telegramId) {
telegramId = req.session.telegramId;
console.log('Telegram ID from session:', telegramId);
// Проверяем, есть ли запись в базе данных
try {
const result = await db.query(
'SELECT provider_id FROM user_identities WHERE user_id = $1 AND provider = $2',
[req.session.userId, 'telegram']
);
console.log('Telegram ID query result:', result.rows);
if (result.rows.length === 0) {
// Если нет, добавляем запись
try {
await db.query(
'INSERT INTO user_identities (user_id, provider, provider_id, created_at) VALUES ($1, $2, $3, NOW()) ON CONFLICT (provider, provider_id) DO NOTHING',
[req.session.userId, 'telegram', telegramId]
);
console.log('Added Telegram ID to database:', telegramId);
} catch (error) {
console.error('Error adding Telegram ID to database:', error);
}
}
} catch (error) {
console.error('Error checking Telegram ID in database:', error);
}
} else {
return res.json({ authenticated: false });
// Если нет, ищем в базе данных
try {
const result = await db.query(
'SELECT provider_id FROM user_identities WHERE user_id = $1 AND provider = $2',
[req.session.userId, 'telegram']
);
console.log('Telegram ID query result:', result.rows);
if (result.rows.length > 0) {
telegramId = result.rows[0].provider_id;
console.log('Telegram ID from database:', telegramId);
// Сохраняем в сессию для будущих запросов
req.session.telegramId = telegramId;
} else {
// Если нет в базе данных, используем фиксированное значение
telegramId = 'Telegram User';
console.log('Using fixed Telegram ID:', telegramId);
// Сохраняем в сессию для будущих запросов
req.session.telegramId = telegramId;
// Добавляем запись в базу данных
try {
await db.query(
'INSERT INTO user_identities (user_id, provider, provider_id, created_at) VALUES ($1, $2, $3, NOW()) ON CONFLICT (provider, provider_id) DO NOTHING',
[req.session.userId, 'telegram', telegramId]
);
console.log('Added Telegram ID to database:', telegramId);
} catch (error) {
console.error('Error adding Telegram ID to database:', error);
}
}
} catch (error) {
console.error('Error fetching Telegram ID:', error);
}
}
} catch (error) {
console.error('Ошибка при проверке аутентификации:', error);
return res.status(500).json({ error: 'Internal server error' });
}
const response = {
authenticated: !!req.session.authenticated,
userId: req.session.userId,
isAdmin: !!req.session.isAdmin,
authType: req.session.authType,
address: req.session.address,
telegramId: telegramId
};
console.log('Auth check response:', response);
res.json(response);
});
// Выход из системы
router.post('/logout', async (req, res) => {
try {
// Сохраняем ID сессии до уничтожения
const sessionID = req.sessionID;
// Уничтожаем сессию
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);
router.post('/logout', (req, res) => {
req.session.destroy((err) => {
if (err) {
console.error('Error destroying session:', err);
return res.status(500).json({ error: 'Failed to logout' });
}
// Очищаем куки
res.clearCookie('connect.sid');
res.json({ success: true });
} catch (error) {
console.error('Ошибка при выходе из системы:', error);
res.status(500).json({ error: 'Ошибка сервера' });
}
});
});
// Маршрут для авторизации через Telegram
@@ -626,12 +697,16 @@ async function checkTokenBalance(address) {
// Маршрут для верификации Telegram
router.post('/telegram/verify', async (req, res) => {
console.log('Telegram verification request body:', req.body);
const { code } = req.body;
try {
const telegramBot = require('../services/telegramBot');
const telegramBot = getBot();
const result = await telegramBot.verifyCode(code);
console.log('Telegram verification result:', result);
if (result.success) {
// Проверяем, что у нас есть telegramId
if (!result.telegramId) {
@@ -678,6 +753,18 @@ router.post('/telegram/verify', async (req, res) => {
});
});
console.log('Telegram ID saved in session:', req.session.telegramId);
// Сохраняем идентификатор Telegram в базе данных
try {
await db.query(
'INSERT INTO user_identities (user_id, provider, provider_id, created_at) VALUES ($1, $2, $3, NOW()) ON CONFLICT (provider, provider_id) DO UPDATE SET user_id = $1',
[userId, 'telegram', result.telegramId]
);
} catch (error) {
console.error('Error saving Telegram ID to database:', error);
}
return res.json({
success: true,
userId: userId,
@@ -1130,4 +1217,64 @@ router.post('/clear-session', async (req, res) => {
}
});
// Инициализация Telegram аутентификации
router.post('/telegram/init', async (req, res) => {
try {
// Проверяем, есть ли уже привязанный Telegram
if (req.session?.userId) {
const existingTelegram = await db.query(
`SELECT provider_id
FROM user_identities
WHERE user_id = $1
AND provider = 'telegram'`,
[req.session.userId]
);
if (existingTelegram.rows.length > 0) {
return res.status(400).json({
error: 'Telegram already linked to this account'
});
}
}
const { verificationCode, botLink } = await initTelegramAuth(req.session);
res.json({ verificationCode, botLink });
} catch (error) {
console.error('Error initializing Telegram auth:', error);
res.status(500).json({ error: 'Failed to initialize Telegram auth' });
}
});
// Инициализация Email аутентификации
router.post('/email/init', async (req, res) => {
try {
const { email } = req.body;
if (!email) {
return res.status(400).json({ error: 'Email is required' });
}
await initEmailAuth(email);
res.json({ success: true });
} catch (error) {
console.error('Error initializing email auth:', error);
res.status(500).json({ error: 'Failed to send verification code' });
}
});
// Проверка кода подтверждения email
router.post('/email/verify', requireAuth, async (req, res) => {
try {
const { code } = req.body;
if (!code) {
return res.status(400).json({ error: 'Verification code is required' });
}
const result = await verifyEmailCode(code, req.session.userId);
res.json(result);
} catch (error) {
console.error('Error verifying email code:', error);
res.status(400).json({ error: error.message });
}
});
module.exports = router;

View File

@@ -10,7 +10,7 @@ const authRouter = require('./routes/auth');
const identitiesRouter = require('./routes/identities');
const { pool } = require('./db');
const helmet = require('helmet');
const TelegramBotService = require('./services/telegramBot');
const { getBot, stopBot } = require('./services/telegramBot');
const pgSession = require('connect-pg-simple')(session);
const authService = require('./services/auth-service');
const logger = require('./utils/logger');
@@ -25,13 +25,34 @@ console.log('Используемый порт:', process.env.PORT || 8000);
async function initServices() {
try {
console.log('Инициализация сервисов...');
if (process.env.TELEGRAM_BOT_TOKEN) {
const telegramBot = new TelegramBotService(process.env.TELEGRAM_BOT_TOKEN);
global.telegramBot = telegramBot; // Сохраняем экземпляр глобально
console.log('Telegram бот инициализирован');
// Останавливаем предыдущий экземпляр бота
await stopBot();
// Добавляем обработку ошибок при запуске бота
try {
await getBot(); // getBot теперь асинхронный и сам запускает бота
console.log('Telegram bot started');
// Добавляем graceful shutdown
process.once('SIGINT', async () => {
await stopBot();
process.exit(0);
});
process.once('SIGTERM', async () => {
await stopBot();
process.exit(0);
});
} catch (error) {
if (error.code === 409) {
logger.warn('Another instance of Telegram bot is running. This is normal during development with nodemon');
// Просто логируем ошибку и продолжаем работу
// Бот будет запущен при следующем перезапуске
} else {
logger.error('Error launching Telegram bot:', error);
}
}
console.log('Все сервисы успешно инициализированы');
} catch (error) {
console.error('Ошибка при инициализации сервисов:', error);

View File

@@ -44,39 +44,41 @@ class AuthService {
*/
async findOrCreateUser(address) {
try {
const existingUser = await db.query(
`SELECT u.id
FROM users u
JOIN user_identities ui ON u.id = ui.user_id
WHERE ui.provider = 'wallet'
AND LOWER(ui.provider_id) = LOWER($1)`,
[address]
);
if (existingUser.rows.length > 0) {
const userId = existingUser.rows[0].id;
const isAdmin = await this.checkAdminRole(address);
return { userId, isAdmin };
// Нормализуем адрес
address = ethers.getAddress(address);
// Ищем пользователя по адресу в таблице user_identities
const userResult = await db.query(`
SELECT u.* FROM users u
JOIN user_identities ui ON u.id = ui.user_id
WHERE ui.provider = 'wallet' AND ui.provider_id = $1
`, [address]);
if (userResult.rows.length > 0) {
const user = userResult.rows[0];
return {
userId: user.id,
isAdmin: user.role === 'admin'
};
}
// Создание нового пользователя
const result = await db.query(
'INSERT INTO users DEFAULT VALUES RETURNING id',
[]
// Если пользователь не найден, создаем нового
const newUserResult = await db.query(
'INSERT INTO users (role) VALUES ($1) RETURNING id',
['user']
);
const userId = result.rows[0].id;
const userId = newUserResult.rows[0].id;
// Добавляем идентификатор кошелька
await db.query(
`INSERT INTO user_identities
(user_id, provider, provider_id, identity_type, identity_value)
VALUES ($1, 'wallet', $2, 'wallet', $2)`,
[userId, address.toLowerCase()]
'INSERT INTO user_identities (user_id, provider, provider_id) VALUES ($1, $2, $3)',
[userId, 'wallet', address]
);
const isAdmin = await this.checkAdminRole(address);
return { userId, isAdmin };
return { userId, isAdmin: false };
} catch (error) {
console.error('Error in findOrCreateUser:', error);
console.error('Error finding or creating user:', error);
throw error;
}
}
@@ -143,41 +145,80 @@ class AuthService {
}
// Создание сессии с проверкой роли
async createSession(req, { userId, address, authType, guestId }) {
let isAdmin = false;
if (address) {
isAdmin = await this.checkAdminRole(address);
} else if (userId) {
const linkedWallet = await this.getLinkedWallet(userId);
if (linkedWallet) {
isAdmin = await this.checkAdminRole(linkedWallet);
async createSession(session, { userId, authenticated, authType, guestId, address }) {
try {
// Обновляем данные сессии
session.userId = userId;
session.authenticated = authenticated;
session.authType = authType;
session.guestId = guestId;
if (address) {
session.address = address;
}
// Сохраняем сессию в БД
const result = await db.query(
`UPDATE session
SET sess = $1
WHERE sid = $2`,
[JSON.stringify({
userId,
authenticated,
authType,
guestId,
address,
cookie: session.cookie
}), session.id]
);
return true;
} catch (error) {
logger.error('Error creating session:', error);
return false;
}
}
req.session.userId = userId;
req.session.address = address;
req.session.isAdmin = isAdmin;
req.session.authenticated = true;
req.session.authType = authType;
if (guestId) {
req.session.guestId = guestId;
async getSession(sessionId) {
try {
const result = await db.query('SELECT * FROM session WHERE sid = $1', [sessionId]);
return result.rows[0];
} catch (error) {
console.error('Error getting session:', error);
throw error;
}
await req.session.save();
}
// Получение связанного кошелька
async getLinkedWallet(userId) {
const result = await db.query(
`SELECT identity_value as address
`SELECT provider_id as address
FROM user_identities
WHERE user_id = $1 AND identity_type = 'wallet'`,
WHERE user_id = $1 AND provider = 'wallet'`,
[userId]
);
return result.rows[0]?.address;
}
/**
* Проверяет роль пользователя Telegram
* @param {number} userId - ID пользователя
* @returns {Promise<string>} - Роль пользователя
*/
async checkUserRole(userId) {
try {
// Проверяем наличие связанного кошелька
const wallet = await this.getLinkedWallet(userId);
if (wallet) {
// Если есть кошелек, проверяем админские токены
const isAdmin = await this.checkAdminRole(wallet);
return isAdmin ? 'admin' : 'user';
}
return 'user';
} catch (error) {
logger.error('Error checking user role:', error);
return 'user';
}
}
}
// Создаем и экспортируем единственный экземпляр

View File

@@ -5,6 +5,7 @@ const simpleParser = require('mailparser').simpleParser;
const { processMessage } = require('./ai-assistant');
const { inspect } = require('util');
const logger = require('../utils/logger');
const { generateVerificationCode, addUserIdentity } = require('../utils/helpers');
// Хранилище кодов подтверждения
const verificationCodes = new Map(); // { email: { code, token, expires } }
@@ -334,6 +335,79 @@ class EmailBotService {
}
}
// Инициализация процесса аутентификации по email
async function initEmailAuth(email) {
const code = generateVerificationCode();
// Сохраняем код на 15 минут
verificationCodes.set(code, {
email,
timestamp: Date.now(),
verified: false
});
// Отправляем код на email
try {
await transporter.sendMail({
from: process.env.SMTP_FROM,
to: email,
subject: 'Код подтверждения для HB3 Accelerator',
text: `Ваш код подтверждения: ${code}\n\nВведите его на сайте для завершения аутентификации.`,
html: `
<h2>Код подтверждения для HB3 Accelerator</h2>
<p>Ваш код подтверждения: <strong>${code}</strong></p>
<p>Введите его на сайте для завершения аутентификации.</p>
`
});
logger.info(`Verification code sent to email: ${email}`);
return { success: true };
} catch (error) {
logger.error('Error sending verification email:', error);
throw new Error('Failed to send verification email');
}
}
// Проверка кода подтверждения
async function verifyEmailCode(code, userId) {
const verification = verificationCodes.get(code);
if (!verification) {
logger.warn(`Invalid verification code attempt: ${code}`);
throw new Error('Неверный код');
}
if (Date.now() - verification.timestamp > 15 * 60 * 1000) {
verificationCodes.delete(code);
logger.warn(`Expired verification code: ${code}`);
throw new Error('Код устарел');
}
try {
// Сохраняем связь пользователя с email
const success = await addUserIdentity(
userId,
'email',
verification.email
);
if (success) {
verificationCodes.delete(code);
logger.info(`User ${userId} successfully linked email ${verification.email}`);
return { success: true };
} else {
throw new Error('Этот email уже привязан к другому пользователю');
}
} catch (error) {
logger.error('Error saving email identity:', error);
throw error;
}
}
// Экспортируем класс и хранилище кодов
module.exports = EmailBotService;
module.exports.verificationCodes = verificationCodes;
module.exports = {
EmailBotService,
verificationCodes,
initEmailAuth,
verifyEmailCode
};

View File

@@ -1,144 +1,183 @@
const TelegramBot = require('node-telegram-bot-api');
const { Telegraf } = require('telegraf');
const logger = require('../utils/logger');
const db = require('../db');
const authService = require('./auth-service');
class TelegramBotService {
constructor(token) {
this.bot = new TelegramBot(token, {
polling: true,
request: {
timeout: 30000 // 30 секунд таймаут
}
});
this.verificationCodes = new Map();
this.setupHandlers();
logger.info('TelegramBot service initialized');
}
let botInstance = null;
const verificationCodes = new Map();
setupHandlers() {
this.bot.on('message', this.handleMessage.bind(this));
this.bot.on('callback_query', this.handleCallbackQuery.bind(this));
// Обработка ошибок
this.bot.on('polling_error', (error) => {
logger.error('Telegram polling error:', error);
});
this.bot.on('error', (error) => {
logger.error('Telegram bot error:', error);
});
}
async handleMessage(msg) {
try {
const chatId = msg.chat.id;
const text = msg.text;
logger.info(`Received message from ${chatId}: ${text}`);
if (text.startsWith('/start')) {
await this.handleStart(msg);
} else if (this.verificationCodes.has(chatId)) {
await this.handleVerificationCode(msg);
}
} catch (error) {
logger.error('Error handling message:', error);
}
}
async handleCallbackQuery(query) {
try {
const chatId = query.message.chat.id;
await this.bot.answerCallbackQuery(query.id);
logger.info(`Handled callback query from ${chatId}`);
} catch (error) {
logger.error('Error handling callback query:', error);
}
}
async handleStart(msg) {
const chatId = msg.chat.id;
try {
await this.bot.sendMessage(
chatId,
'Добро пожаловать! Используйте этого бота для аутентификации в приложении.'
);
logger.info(`Sent welcome message to ${chatId}`);
} catch (error) {
logger.error(`Error sending welcome message to ${chatId}:`, error);
}
}
async handleVerificationCode(msg) {
const chatId = msg.chat.id;
const code = msg.text.trim();
try {
const verificationData = this.verificationCodes.get(chatId);
if (!verificationData) {
await this.bot.sendMessage(chatId, 'Нет активного кода подтверждения.');
return;
}
if (Date.now() > verificationData.expires) {
this.verificationCodes.delete(chatId);
await this.bot.sendMessage(chatId, 'Код подтверждения истек. Запросите новый.');
return;
}
if (verificationData.code === code) {
await this.bot.sendMessage(chatId, 'Код подтвержден успешно!');
this.verificationCodes.delete(chatId);
} else {
await this.bot.sendMessage(chatId, 'Неверный код. Попробуйте еще раз.');
}
} catch (error) {
logger.error(`Error handling verification code for ${chatId}:`, error);
}
}
async sendVerificationCode(chatId, code) {
try {
// Сохраняем код с временем истечения (15 минут)
this.verificationCodes.set(chatId, {
code,
expires: Date.now() + 15 * 60 * 1000
});
await this.bot.sendMessage(
chatId,
`Ваш код подтверждения: ${code}\nВведите его в приложении.`
);
logger.info(`Sent verification code to ${chatId}`);
return true;
} catch (error) {
logger.error(`Error sending verification code to ${chatId}:`, error);
return false;
}
}
async verifyCode(code) {
try {
for (const [chatId, data] of this.verificationCodes.entries()) {
if (data.code === code) {
if (Date.now() > data.expires) {
this.verificationCodes.delete(chatId);
return { success: false, error: 'Код истек' };
}
this.verificationCodes.delete(chatId);
return { success: true, telegramId: chatId.toString() };
}
}
return { success: false, error: 'Неверный код' };
} catch (error) {
logger.error('Error verifying code:', error);
return { success: false, error: 'Внутренняя ошибка' };
}
// Простая остановка бота
async function stopBot() {
if (botInstance) {
await botInstance.stop();
botInstance = null;
}
}
module.exports = TelegramBotService;
// Создание и настройка бота
async function getBot() {
if (!botInstance) {
botInstance = new Telegraf(process.env.TELEGRAM_BOT_TOKEN);
// Обработка команды /start
botInstance.command('start', (ctx) => {
ctx.reply('Добро пожаловать! Отправьте код подтверждения для аутентификации.');
});
// Обработка кодов верификации
botInstance.on('text', async (ctx) => {
const code = ctx.message.text.trim();
const verification = verificationCodes.get(code);
if (!verification) {
ctx.reply('Неверный код подтверждения');
return;
}
try {
logger.info('Starting Telegram auth process for code:', code);
logger.info('Verification data:', verification);
// Сначала проверяем, существует ли пользователь с этим Telegram ID
let userId;
const existingUser = await db.query(
`SELECT u.id
FROM users u
JOIN user_identities ui ON u.id = ui.user_id
WHERE ui.provider = $1
AND ui.provider_id = $2`,
['telegram', ctx.from.id.toString()]
);
if (existingUser.rows.length > 0) {
userId = existingUser.rows[0].id;
logger.info('Found existing user with ID:', userId);
} else {
// Создаем нового пользователя
const result = await db.query(
`INSERT INTO users (created_at, updated_at)
VALUES (NOW(), NOW())
RETURNING id`,
[]
);
userId = result.rows[0].id;
logger.info('Created new user with ID:', userId);
}
// Связываем Telegram с пользователем
await db.query(
`INSERT INTO user_identities
(user_id, provider, provider_id, created_at)
VALUES ($1, $2, $3, NOW())
ON CONFLICT (provider, provider_id) DO UPDATE SET user_id = $1`,
[userId, 'telegram', ctx.from.id.toString()]
);
logger.info(`User ${userId} successfully linked Telegram account ${ctx.from.id}`);
// Обновляем сессию
if (verification?.session) {
logger.info('Creating session with data:', verification.session);
// Обновляем данные сессии напрямую
verification.session.userId = userId;
verification.session.authenticated = true;
verification.session.authType = 'telegram';
verification.session.telegramId = ctx.from.id.toString(); // Добавляем идентификатор Telegram в сессию
// Проверяем роль пользователя
const userRole = await authService.checkUserRole(userId);
verification.session.userRole = userRole;
await new Promise((resolve, reject) => {
verification.session.save(err => {
if (err) reject(err);
else resolve();
});
});
logger.info('Session created successfully');
}
// Отправляем последнее сообщение пользователя
if (verification.session.guestId) {
logger.info('Fetching last guest message for guestId:', verification.session.guestId);
const messageResult = await db.query(`
SELECT content FROM guest_messages
WHERE guest_id = $1
ORDER BY created_at DESC
LIMIT 1
`, [verification.session.guestId]);
const lastMessage = messageResult.rows[0]?.content;
logger.info('Found last message:', lastMessage);
if (lastMessage) {
await ctx.reply(`Ваше последнее сообщение: "${lastMessage}"`);
}
}
// Отправляем сообщение об успешной аутентификации
await ctx.reply('Аутентификация успешна! Можете вернуться в приложение.');
// Удаляем сообщение с кодом
try {
await ctx.deleteMessage(ctx.message.message_id);
} catch (error) {
logger.warn('Could not delete code message:', error);
}
// Удаляем код верификации
verificationCodes.delete(code);
} catch (error) {
logger.error('Error in Telegram auth:', error);
// Более информативные сообщения об ошибках
let errorMessage = 'Произошла ошибка при сохранении. Попробуйте позже.';
if (error.code === '42P01') {
errorMessage = 'Ошибка сессии. Пожалуйста, обновите страницу и попробуйте снова.';
} else if (error.code === '42703') {
errorMessage = 'Ошибка структуры данных. Обратитесь к администратору.';
}
if (error.code) {
logger.error('Database error code:', error.code);
}
if (error.detail) {
logger.error('Error detail:', error.detail);
}
if (error.stack) {
logger.error('Error stack:', error.stack);
}
await ctx.reply(errorMessage);
}
});
// Запускаем бота
await botInstance.launch();
}
return botInstance;
}
// Инициализация процесса аутентификации
async function initTelegramAuth(session) {
const code = Math.random().toString(36).substring(2, 8).toUpperCase();
const botLink = `https://t.me/${process.env.TELEGRAM_BOT_USERNAME}`;
verificationCodes.set(code, {
timestamp: Date.now(),
session: session
});
logger.info(`Generated verification code: ${code} for Telegram auth`);
return { verificationCode: code, botLink };
}
module.exports = {
getBot,
stopBot,
verificationCodes,
initTelegramAuth
};

View File

@@ -1,3 +1,5 @@
const db = require('../db');
// Функция для создания задержки
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
@@ -9,7 +11,43 @@ function isValidEmail(email) {
return emailRegex.test(email);
}
// Генерация кода подтверждения
function generateVerificationCode(length = 6) {
return Math.random()
.toString(36)
.substring(2, 2 + length)
.toUpperCase();
}
// Проверка существования идентификатора пользователя
async function checkUserIdentity(userId, provider, providerId) {
const result = await db.query(
'SELECT * FROM user_identities WHERE user_id = $1 AND provider = $2 AND provider_id = $3',
[userId, provider, providerId]
);
return result.rows.length > 0;
}
// Добавление новой идентификации
async function addUserIdentity(userId, provider, providerId) {
try {
await db.query(
'INSERT INTO user_identities (user_id, provider, provider_id) VALUES ($1, $2, $3)',
[userId, provider, providerId]
);
return true;
} catch (error) {
if (error.code === '23505') { // Уникальное ограничение нарушено
return false;
}
throw error;
}
}
module.exports = {
sleep,
isValidEmail,
generateVerificationCode,
checkUserIdentity,
addUserIdentity
};

View File

@@ -1079,6 +1079,11 @@
resolved "https://registry.npmjs.org/@stablelib/wipe/-/wipe-1.0.1.tgz"
integrity sha512-WfqfX/eXGiAd3RJe4VU2snh/ZPwtSjLG4ynQ/vYzvghTh7dHFcI1wl+nrkWG6lGhukOxOsUHfv8dUXr58D0ayg==
"@telegraf/types@^7.1.0":
version "7.1.0"
resolved "https://registry.yarnpkg.com/@telegraf/types/-/types-7.1.0.tgz#d8bd9b2f5070b4de46971416e890338cd89fc23d"
integrity sha512-kGevOIbpMcIlCDeorKGpwZmdH7kHbqlk/Yj6dEpJMKEQw5lk0KVQY0OLXaCswy8GqlIVLd5625OB+rAntP9xVw==
"@tsconfig/node10@^1.0.7":
version "1.0.11"
resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2"
@@ -1806,6 +1811,24 @@ bs58check@^2.1.2:
create-hash "^1.1.0"
safe-buffer "^5.1.2"
buffer-alloc-unsafe@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0"
integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==
buffer-alloc@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec"
integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==
dependencies:
buffer-alloc-unsafe "^1.1.0"
buffer-fill "^1.0.0"
buffer-fill@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c"
integrity sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==
buffer-from@^1.0.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
@@ -2288,7 +2311,7 @@ debug@2.6.9:
dependencies:
ms "2.0.0"
debug@4, debug@^4, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.5:
debug@4, debug@^4, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5:
version "4.4.0"
resolved "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz"
integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==
@@ -4678,6 +4701,11 @@ mocha@^10.0.0, mocha@^10.2.0:
yargs-parser "^20.2.9"
yargs-unparser "^2.0.0"
mri@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b"
integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==
ms@2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz"
@@ -4753,7 +4781,7 @@ node-emoji@^1.10.0:
dependencies:
lodash "^4.17.21"
node-fetch@^2.6.7:
node-fetch@^2.6.7, node-fetch@^2.7.0:
version "2.7.0"
resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz"
integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
@@ -5011,6 +5039,11 @@ p-timeout@^3.2.0:
dependencies:
p-finally "^1.0.0"
p-timeout@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-4.1.0.tgz#788253c0452ab0ffecf18a62dff94ff1bd09ca0a"
integrity sha512-+/wmHtzJuWii1sXn3HCuH/FTwGhrp4tmJTxSKJbfS+vkipci6osxXM5mY0jUiRzWKMTgUT8l7HFbeSwZAynqHw==
parent-module@^1.0.0:
version "1.0.1"
resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz"
@@ -5555,6 +5588,13 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1:
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
safe-compare@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/safe-compare/-/safe-compare-1.1.4.tgz#5e0128538a82820e2e9250cd78e45da6786ba593"
integrity sha512-b9wZ986HHCo/HbKrRpBJb2kqXMK9CEWIE1egeEvZsYn69ay3kdfl9nG3RyOcR+jInTDf7a86WQ1d4VJX7goSSQ==
dependencies:
buffer-alloc "^1.2.0"
safe-push-apply@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz"
@@ -5582,6 +5622,11 @@ safe-stable-stringify@^2.3.1:
resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
sandwich-stream@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/sandwich-stream/-/sandwich-stream-2.0.2.tgz#6d1feb6cf7e9fe9fadb41513459a72c2e84000fa"
integrity sha512-jLYV0DORrzY3xaz/S9ydJL6Iz7essZeAfnAavsJ+zsJGZ1MOnsS52yRjU3uF3pJa/lla7+wisp//fxOwOH8SKQ==
sc-istanbul@^0.4.5:
version "0.4.6"
resolved "https://registry.yarnpkg.com/sc-istanbul/-/sc-istanbul-0.4.6.tgz#cf6784355ff2076f92d70d59047d71c13703e839"
@@ -6153,6 +6198,20 @@ table@^6.8.0:
string-width "^4.2.3"
strip-ansi "^6.0.1"
telegraf@^4.16.3:
version "4.16.3"
resolved "https://registry.yarnpkg.com/telegraf/-/telegraf-4.16.3.tgz#f03fa30482b540a7f9895af8f13ec8f432840a66"
integrity sha512-yjEu2NwkHlXu0OARWoNhJlIjX09dRktiMQFsM678BAH/PEPVwctzL67+tvXqLCRQQvm3SDtki2saGO9hLlz68w==
dependencies:
"@telegraf/types" "^7.1.0"
abort-controller "^3.0.0"
debug "^4.3.4"
mri "^1.2.0"
node-fetch "^2.7.0"
p-timeout "^4.1.0"
safe-compare "^1.1.4"
sandwich-stream "^2.0.2"
text-hex@1.0.x:
version "1.0.0"
resolved "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz"

View File

@@ -0,0 +1,78 @@
import { ref, onMounted } from 'vue';
import axios from '../api/axios';
export function useAuth() {
const isAuthenticated = ref(false);
const authType = ref(null);
const userId = ref(null);
const address = ref(null);
const telegramInfo = ref(null);
const isAdmin = ref(false);
const telegramId = ref(null);
const updateAuth = ({ authenticated, authType: newAuthType, userId: newUserId, address: newAddress, telegramId: newTelegramId, isAdmin: newIsAdmin }) => {
isAuthenticated.value = authenticated;
authType.value = newAuthType;
userId.value = newUserId;
address.value = newAddress;
telegramId.value = newTelegramId;
isAdmin.value = newIsAdmin;
};
const checkAuth = async () => {
try {
const response = await axios.get('/api/auth/check');
console.log('Auth check response:', response.data);
updateAuth(response.data);
return response.data;
} catch (error) {
console.error('Error checking auth status:', error);
return { authenticated: false };
}
};
const disconnect = async () => {
try {
await axios.post('/api/auth/logout');
updateAuth({
authenticated: false,
authType: null,
userId: null,
address: null,
telegramId: null,
isAdmin: false
});
// Очищаем localStorage
localStorage.removeItem('isAuthenticated');
localStorage.removeItem('userId');
localStorage.removeItem('address');
localStorage.removeItem('isAdmin');
// Перезагружаем страницу
window.location.reload();
return { success: true };
} catch (error) {
console.error('Error disconnecting:', error);
return { success: false, error: error.message };
}
};
onMounted(async () => {
await checkAuth();
});
return {
isAuthenticated,
authType,
userId,
address,
telegramInfo,
isAdmin,
telegramId,
updateAuth,
checkAuth,
disconnect
};
}

View File

@@ -1,77 +1,90 @@
import { ethers } from 'ethers';
import axios from 'axios';
import axios from '../api/axios';
import { SiweMessage } from 'siwe';
export async function connectWithWallet() {
console.log('Starting wallet connection...');
try {
console.log('Starting wallet connection...');
// Проверяем наличие MetaMask
if (!window.ethereum) {
throw new Error('MetaMask не установлен. Пожалуйста, установите MetaMask');
throw new Error('MetaMask not detected. Please install MetaMask.');
}
console.log('MetaMask detected, requesting accounts...');
const accounts = await window.ethereum.request({
method: 'eth_requestAccounts'
});
// Запрашиваем доступ к аккаунтам
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
console.log('Got accounts:', accounts);
if (!accounts || accounts.length === 0) {
throw new Error('Нет доступных аккаунтов. Пожалуйста, разблокируйте MetaMask');
throw new Error('No accounts found. Please unlock MetaMask.');
}
// Берем первый аккаунт
const address = ethers.getAddress(accounts[0]);
console.log('Normalized address:', address);
// Запрашиваем nonce с сервера
console.log('Requesting nonce...');
const { data: { nonce } } = await axios.get('/api/auth/nonce', {
params: { address }
});
const nonceResponse = await axios.get(`/api/auth/nonce?address=${address}`);
const nonce = nonceResponse.data.nonce;
console.log('Got nonce:', nonce);
// Формируем сообщение в формате SIWE (Sign-In with Ethereum)
// Создаем сообщение для подписи
const domain = window.location.host;
const origin = window.location.origin;
const statement = "Sign in with Ethereum to the app.";
const message = [
`${domain} wants you to sign in with your Ethereum account:`,
const statement = 'Sign in with Ethereum to the app.';
const siweMessage = new SiweMessage({
domain,
address,
"",
statement,
"",
`URI: ${origin}`,
"Version: 1",
"Chain ID: 1",
`Nonce: ${nonce}`,
`Issued At: ${new Date().toISOString()}`,
"Resources:",
`- ${origin}/api/auth/verify`
].join("\n");
uri: origin,
version: '1',
chainId: 1,
nonce,
resources: [`${origin}/api/auth/verify`]
});
const message = siweMessage.prepareMessage();
console.log('SIWE message:', message);
// Запрашиваем подпись
console.log('Requesting signature...');
const signature = await window.ethereum.request({
method: 'personal_sign',
params: [message, address]
});
console.log('Got signature:', signature);
// Отправляем подпись на сервер для верификации
console.log('Sending verification request...');
const response = await axios.post('/api/auth/verify', {
address,
const verificationResponse = await axios.post('/api/auth/verify', {
message,
signature,
message
address
});
console.log('Verification response:', response.data);
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
return { address, signer };
console.log('Verification response:', verificationResponse.data);
// Обновляем состояние аутентификации
if (verificationResponse.data.success) {
// Обновляем состояние аутентификации в localStorage
localStorage.setItem('isAuthenticated', 'true');
localStorage.setItem('userId', verificationResponse.data.userId);
localStorage.setItem('address', verificationResponse.data.address);
localStorage.setItem('isAdmin', verificationResponse.data.isAdmin);
// Перезагружаем страницу для обновления состояния
window.location.reload();
}
return verificationResponse.data;
} catch (error) {
// Форматируем ошибку для пользователя
const message = error.message || 'Ошибка подключения кошелька';
console.error('Error connecting wallet:', message);
throw new Error(message);
console.error('Error connecting wallet:', error);
throw error;
}
}

View File

@@ -6,19 +6,30 @@
<h3>Венчурный фонд и поставщик программного обеспечения</h3>
</div>
<div class="chat-container">
<div class="chat-header">
<!-- Используем тот же компонент, что и в сообщениях -->
<div v-if="!isAuthenticated" class="auth-buttons">
<div v-if="!isAuthenticated && !isConnecting" class="auth-buttons">
<button class="auth-btn wallet-btn" @click="handleWalletAuth">
<span class="auth-icon">👛</span> Подключить кошелек
</button>
</div>
<div v-else class="wallet-info">
<span>{{ truncateAddress(auth.address.value) }}</span>
<button class="disconnect-btn" @click="disconnectWallet">
Отключить кошелек
<div v-if="isConnecting" class="connecting-info">
<span>Подключение кошелька...</span>
</div>
<div v-show="isAuthenticated && auth.authType.value === 'wallet'" class="auth-buttons">
<span>{{ auth.address && auth.address.value ? truncateAddress(auth.address.value) : '' }}</span>
<button class="auth-btn wallet-btn" @click="disconnectWallet">
<span class="auth-icon">🔌</span> Отключить кошелек
</button>
</div>
<div v-show="isAuthenticated && auth.authType.value === 'telegram'" class="auth-buttons">
<span>Telegram: {{ auth.telegramId }}</span>
<button class="auth-btn disconnect-btn" @click="disconnectWallet">
<span class="auth-icon">🔌</span> Выйти
</button>
</div>
</div>
@@ -44,52 +55,35 @@
<button class="auth-btn wallet-btn" @click="handleWalletAuth">
<span class="auth-icon">👛</span> Подключить кошелек
</button>
<button class="auth-btn telegram-btn" @click="handleTelegramAuth">
<!-- Telegram верификация -->
<div v-if="showTelegramVerification" class="verification-block">
<div class="verification-code">
<span>Код подтверждения:</span>
<code @click="copyCode(telegramVerificationCode)">{{ telegramVerificationCode }}</code>
</div>
<a :href="telegramBotLink" target="_blank" class="bot-link">
<span class="auth-icon">📱</span> Открыть HB3_Accelerator_Bot
</a>
</div>
<button v-else class="auth-btn telegram-btn" @click="handleTelegramAuth">
<span class="auth-icon">📱</span> Подключить Telegram
</button>
<button class="auth-btn email-btn" @click="handleEmailAuth">
<!-- Email верификация -->
<div v-if="showEmailVerification" class="verification-block">
<div class="verification-code">
<span>Код подтверждения:</span>
<code @click="copyCode(emailVerificationCode)">{{ emailVerificationCode }}</code>
</div>
<a :href="'mailto:' + emailInput" class="bot-link">
<span class="auth-icon"></span> Открыть почту
</a>
</div>
<button v-else class="auth-btn email-btn" @click="handleEmailAuth">
<span class="auth-icon"></span> Подключить Email
</button>
</div>
<!-- Email форма -->
<div v-if="showEmailForm" class="auth-form">
<input
v-model="emailInput"
type="email"
placeholder="Введите ваш email"
class="auth-input"
/>
<button @click="submitEmail" class="auth-btn">
Отправить код
</button>
</div>
<!-- Форма верификации email -->
<div v-if="showEmailVerification" class="auth-form">
<input
v-model="emailCode"
type="text"
placeholder="Введите код из email"
class="auth-input"
/>
<button @click="verifyEmailCode" class="auth-btn">
Подтвердить
</button>
</div>
<!-- Telegram верификация -->
<div v-if="showTelegramVerification" class="auth-form">
<input
v-model="telegramCode"
type="text"
placeholder="Введите код из Telegram"
class="auth-input"
/>
<button @click="verifyTelegramCode" class="auth-btn">
Подтвердить
</button>
</div>
<div v-if="emailError" class="error-message">
{{ emailError }}
@@ -113,6 +107,18 @@
</button>
</div>
</div>
<!-- В шаблоне, где отображается информация о пользователе -->
<div v-if="auth.isAuthenticated" class="auth-info">
<div v-if="auth.authType === 'wallet'">
<span>Подключен кошелек: {{ auth.address }}</span>
<button @click="disconnectWallet">Отключить кошелек</button>
</div>
<div v-if="auth.authType === 'telegram'">
<span>Подключен Telegram: {{ auth.telegramId }}</span>
<button @click="disconnectWallet">Выйти</button>
</div>
</div>
</div>
</template>
@@ -123,50 +129,114 @@ import TelegramConnect from '../components/identity/TelegramConnect.vue';
import EmailConnect from '../components/identity/EmailConnect.vue';
import api from '../api/axios';
import { connectWithWallet } from '../services/wallet';
import axios from 'axios';
import { useAuth } from '../composables/useAuth';
console.log('HomeView.vue: Version with chat loaded');
const auth = inject('auth');
const auth = useAuth();
const isAuthenticated = computed(() => auth.isAuthenticated.value);
const authType = ref(null);
const isConnecting = ref(false);
const messages = ref([]);
const guestMessages = ref([]);
const newMessage = ref('');
const isLoading = ref(false);
const messagesContainer = ref(null);
const userLanguage = ref('ru');
const email = ref('');
const isValidEmail = ref(true);
const hasShownAuthMessage = ref(false);
const hasShownAuthOptions = ref(false);
// Email аутентификация
const emailVerificationCode = ref('');
const showEmailVerification = ref(false);
const emailErrorMessage = ref('');
// Добавляем состояния для форм верификации
const showTelegramVerification = ref(false);
const showEmailForm = ref(false);
const telegramCode = ref('');
const emailInput = ref('');
const emailCode = ref('');
const emailError = ref('');
// Добавляем состояния для пагинации
const PAGE_SIZE = 2; // Показываем только последнее сообщение и ответ
const allMessages = ref([]); // Все загруженные сообщения
const currentPage = ref(1); // Текущая страница
const hasMoreMessages = ref(true); // Есть ли еще сообщения
const isLoadingMore = ref(false); // Загружаются ли дополнительные сообщения
const isLoadingMore = ref(false);
const hasMoreMessages = ref(false);
const offset = ref(0);
const limit = ref(20);
// Вычисляемое свойство для отображаемых сообщений
const displayedMessages = computed(() => {
const startIndex = Math.max(allMessages.value.length - (PAGE_SIZE * currentPage.value), 0);
return allMessages.value.slice(startIndex);
});
// Состояния для верификации
const showTelegramVerification = ref(false);
const telegramVerificationCode = ref('');
const telegramBotLink = ref('');
const telegramAuthCheckInterval = ref(null);
const showEmailVerification = ref(false);
const emailVerificationCode = ref('');
const emailInput = ref('');
const emailError = ref('');
// Функция для копирования кода
const copyCode = (code) => {
navigator.clipboard.writeText(code);
// Можно добавить уведомление о копировании
};
// Функция для показа ошибок
const showError = (message) => {
// Можно использовать toast или alert
alert(message);
};
// Обработчик для Telegram аутентификации
const handleTelegramAuth = async () => {
try {
const { data } = await axios.post('/api/auth/telegram/init');
const { verificationCode, botLink } = data;
// Показываем код верификации
showTelegramVerification.value = true;
telegramVerificationCode.value = verificationCode;
telegramBotLink.value = botLink;
// Запускаем проверку статуса аутентификации
telegramAuthCheckInterval.value = setInterval(async () => {
try {
const response = await axios.get('/api/auth/check');
if (response.data.authenticated) {
auth.updateAuth({
isAuthenticated: true,
authType: response.data.authType,
userId: response.data.userId
});
clearInterval(telegramAuthCheckInterval.value);
telegramAuthCheckInterval.value = null;
showTelegramVerification.value = false;
// Перезагружаем страницу для полного обновления состояния
window.location.reload();
}
} catch (error) {
console.error('Error checking auth status:', error);
}
}, 2000);
// Очищаем интервал через 5 минут
setTimeout(() => {
if (telegramAuthCheckInterval.value) {
clearInterval(telegramAuthCheckInterval.value);
telegramAuthCheckInterval.value = null;
showTelegramVerification.value = false;
}
}, 5 * 60 * 1000);
} catch (error) {
console.error('Error initializing Telegram auth:', error);
showError('Ошибка при инициализации Telegram аутентификации');
}
};
// Обработчик для Email аутентификации
const handleEmailAuth = async () => {
try {
// Запрашиваем email у пользователя
const email = prompt('Введите ваш email:');
if (!email) return;
const { data } = await axios.post('/api/auth/email/init', { email });
if (data.success) {
showEmailVerification.value = true;
emailInput.value = email;
}
} catch (error) {
console.error('Error initializing email auth:', error);
emailError.value = error.response?.data?.error || 'Ошибка отправки кода';
}
};
// Функция для сокращения адреса кошелька
const truncateAddress = (address) => {
@@ -246,38 +316,32 @@ watch(() => isAuthenticated.value, async (newValue) => {
}
});
// Находим существующую функцию handleWalletAuth и обновляем её
// Функция для подключения кошелька
const handleWalletAuth = async () => {
if (isConnecting.value || isAuthenticated.value) return; // Предотвращаем повторное подключение
isConnecting.value = true;
try {
const result = await connectWithWallet();
await auth.checkAuth();
console.log('Wallet connection result:', result);
if (result.authenticated) {
// Сохраняем гостевые сообщения перед очисткой
const guestMessages = [...messages.value];
messages.value = [];
offset.value = 0;
hasMoreMessages.value = true;
if (result.success) {
// Обновляем состояние авторизации
await auth.checkAuth();
try {
await api.post('/api/chat/link-guest-messages');
console.log('Guest messages linked to authenticated user');
await loadMoreMessages();
const filteredGuestMessages = guestMessages
.filter(msg => !msg.showAuthButtons)
.reverse();
messages.value = [...messages.value, ...filteredGuestMessages];
await nextTick();
scrollToBottom();
} catch (linkError) {
console.error('Error linking guest messages:', linkError);
}
// Добавляем небольшую задержку перед сбросом состояния isConnecting
setTimeout(() => {
isConnecting.value = false;
}, 500);
return;
} else {
console.error('Failed to connect wallet:', result.error);
}
} catch (error) {
console.error('Error connecting wallet:', error);
}
isConnecting.value = false;
};
// Функция для сохранения гостевых сообщений на сервере
@@ -302,103 +366,6 @@ const saveGuestMessagesToServer = async () => {
}
};
// Функция для подключения через Telegram
async function connectTelegram() {
try {
// Отправляем запрос на получение ссылки для авторизации через Telegram
const response = await api.get('/api/auth/telegram', {
withCredentials: true
});
if (response.data.error) {
messages.value.push({
sender: 'ai',
text: `Ошибка при подключении Telegram: ${response.data.error}`,
timestamp: new Date(),
});
return;
}
if (response.data.authUrl) {
messages.value.push({
sender: 'ai',
text: `Для подключения Telegram, перейдите по <a href="${response.data.authUrl}" target="_blank">этой ссылке</a> и авторизуйтесь.`,
timestamp: new Date(),
});
// Открываем ссылку в новом окне
window.open(response.data.authUrl, '_blank');
} else {
messages.value.push({
sender: 'ai',
text: 'Для подключения Telegram, перейдите по <a href="https://t.me/YourBotName" target="_blank">этой ссылке</a> и авторизуйтесь.',
timestamp: new Date(),
});
}
} catch (error) {
console.error('Error connecting with Telegram:', error);
messages.value.push({
sender: 'ai',
text: 'Извините, произошла ошибка при подключении Telegram. Пожалуйста, попробуйте позже.',
timestamp: new Date(),
});
}
}
// Запрос кода подтверждения по email
async function requestEmailCode() {
emailErrorMessage.value = '';
try {
const response = await auth.requestEmailVerification(email.value);
if (response.success) {
showEmailVerification.value = true;
// Временно для тестирования
if (response.verificationCode) {
emailErrorMessage.value = `Код для тестирования: ${response.verificationCode}`;
}
} else {
emailErrorMessage.value = response.error || 'Ошибка запроса кода подтверждения';
}
} catch (error) {
console.error('Error requesting email verification:', error);
emailErrorMessage.value = 'Ошибка запроса кода подтверждения';
}
}
// Функция проверки кода
const verifyEmailCode = async () => {
try {
const response = await api.post('/api/auth/email/verify-code', {
email: emailInput.value,
code: emailCode.value
});
if (response.data.success) {
auth.setEmailAuth(response.data);
showEmailVerification.value = false;
emailError.value = '';
// Загружаем историю чата после успешной аутентификации
await loadMoreMessages();
} else {
emailError.value = response.data.error || 'Неверный код';
}
} catch (error) {
emailError.value = error.response?.data?.error || 'Ошибка проверки кода';
console.error('Error verifying email code:', error);
}
};
// Отмена верификации email
function cancelEmailVerification() {
showEmailVerification.value = false;
emailVerificationCode.value = '';
emailErrorMessage.value = '';
}
// Форматирование времени
const formatTime = (timestamp) => {
if (!timestamp) return '';
@@ -499,97 +466,10 @@ const handleMessage = async (text) => {
}
};
// Добавляем методы для аутентификации
const handleTelegramAuth = () => {
window.open('https://t.me/HB3_Accelerator_Bot', '_blank');
// Показываем форму для ввода кода через небольшую задержку
setTimeout(() => {
showTelegramVerification.value = true;
}, 1000);
};
const handleEmailAuth = async () => {
showEmailForm.value = true;
};
// Функция отправки email
const submitEmail = async () => {
try {
const response = await api.post('/api/auth/email/request', {
email: emailInput.value
});
if (response.data.success) {
showEmailForm.value = false;
showEmailVerification.value = true;
} else {
emailError.value = response.data.error || 'Ошибка отправки кода';
}
} catch (error) {
emailError.value = 'Ошибка отправки кода';
console.error('Error sending email code:', error);
}
};
// Функция верификации кода Telegram
const verifyTelegramCode = async () => {
try {
const response = await api.post('/api/auth/telegram/verify', {
code: telegramCode.value
});
if (response.data.success) {
console.log('Telegram verification successful:', response.data);
// Обновляем состояние аутентификации
auth.setAuth({
isAuthenticated: response.data.authenticated,
userId: response.data.userId,
telegramId: response.data.telegramId,
isAdmin: response.data.isAdmin,
authType: 'telegram'
});
showTelegramVerification.value = false;
telegramCode.value = '';
// Показываем сообщение об успехе
messages.value.push({
id: Date.now(),
content: 'Telegram успешно подключен!',
role: 'assistant',
timestamp: new Date().toISOString()
});
// Загружаем историю чата после небольшой задержки
setTimeout(async () => {
await loadMoreMessages();
}, 100);
} else {
messages.value.push({
id: Date.now(),
content: response.data.error || 'Ошибка верификации кода',
role: 'assistant',
timestamp: new Date().toISOString()
});
}
} catch (error) {
console.error('Error verifying Telegram code:', error);
messages.value.push({
id: Date.now(),
content: 'Произошла ошибка. Пожалуйста, попробуйте позже.',
role: 'assistant',
timestamp: new Date().toISOString()
});
}
};
const disconnectWallet = async () => {
try {
await auth.disconnect();
messages.value = [];
offset.value = 0;
hasMoreMessages.value = true;
console.log('Wallet disconnected successfully');
} catch (error) {
console.error('Error disconnecting wallet:', error);
}
@@ -612,6 +492,20 @@ onMounted(() => {
if (messagesContainer.value) {
messagesContainer.value.addEventListener('scroll', handleScroll);
}
console.log('Auth state on mount:', {
isAuthenticated: auth.isAuthenticated.value,
authType: auth.authType.value,
telegramId: auth.telegramId.value
});
// Добавляем отладочный вывод для auth.authType
console.log('auth.authType:', auth.authType);
console.log('auth.authType.value:', auth.authType.value);
console.log('auth.authType.value === "telegram":', auth.authType.value === 'telegram');
});
watch(() => auth.telegramId.value, (newValue) => {
console.log('Telegram ID changed:', newValue);
});
onBeforeUnmount(() => {
@@ -619,6 +513,9 @@ onBeforeUnmount(() => {
if (messagesContainer.value) {
messagesContainer.value.removeEventListener('scroll', handleScroll);
}
if (telegramAuthCheckInterval.value) {
clearInterval(telegramAuthCheckInterval.value);
}
});
</script>
@@ -897,7 +794,7 @@ h1 {
box-sizing: border-box;
}
.auth-btn {
.auth-btn, .disconnect-btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
@@ -905,34 +802,43 @@ h1 {
font-size: 14px;
display: flex;
align-items: center;
gap: 8px;
justify-content: center;
margin: 0;
}
.wallet-btn {
background-color: #4a5568;
background-color: #4CAF50;
color: white;
}
.wallet-btn:hover {
background-color: #2d3748;
background-color: #45a049;
}
.disconnect-btn {
background-color: #f44336;
color: white;
}
.disconnect-btn:hover {
background-color: #d32f2f;
}
.auth-buttons, .wallet-info {
display: flex;
align-items: center;
gap: 10px;
}
.auth-icon {
font-size: 16px;
margin-right: 5px;
}
.telegram-btn {
background-color: #0088cc;
.connecting-info {
padding: 8px 16px;
background-color: #2196F3;
color: white;
}
.email-btn {
background-color: #4caf50;
color: white;
}
.cancel-btn {
background-color: #999;
border-radius: 4px;
}
.error-message {
@@ -963,13 +869,17 @@ h1 {
color: white;
}
.wallet-btn:hover {
background-color: #2d3748;
}
.telegram-btn {
background-color: #0088cc;
color: white;
}
.email-btn {
background-color: #48bb78;
background-color: #4caf50;
color: white;
}
@@ -1102,4 +1012,103 @@ h1 {
padding: 1rem;
color: #666;
}
/* Добавляем отображение кода и ссылки для Telegram */
.verification-info {
padding: 10px;
background-color: #f9f9f9;
border: 1px solid #ddd;
border-radius: 4px;
margin-top: 10px;
}
.verification-info p {
margin: 5px 0;
}
.verification-info strong {
font-weight: bold;
}
.verification-info a {
color: #007bff;
text-decoration: none;
}
.verification-info a:hover {
text-decoration: underline;
}
.verification-block {
display: flex;
flex-direction: column;
gap: 8px;
padding: 10px;
background: #f5f5f5;
border-radius: 8px;
margin: 8px 0;
}
.verification-code {
display: flex;
align-items: center;
gap: 8px;
}
.verification-code code {
background: #fff;
padding: 4px 8px;
border-radius: 4px;
font-family: monospace;
cursor: pointer;
border: 1px solid #ddd;
}
.verification-code code:hover {
background: #f0f0f0;
}
.bot-link {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 8px 16px;
background: #0088cc;
color: white;
text-decoration: none;
border-radius: 4px;
transition: background-color 0.2s;
}
.bot-link:hover {
background: #006699;
}
.auth-icon {
font-size: 1.2em;
}
/* Добавляем новые стили для информации о пользователе */
.auth-info {
margin-top: 10px;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
background-color: #f9f9f9;
}
.auth-info button {
padding: 8px 16px;
background-color: #ff4444;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
margin-top: 10px;
}
.auth-info button:hover {
background-color: #cc0000;
}
</style>