Описание изменений
This commit is contained in:
@@ -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": {
|
||||
|
||||
@@ -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);
|
||||
const { address, message, signature } = req.body;
|
||||
|
||||
// Проверяем подпись через SIWE
|
||||
const siwe = new SiweMessage(message);
|
||||
console.log('Created SIWE message object');
|
||||
|
||||
const fields = await siwe.verify({ signature });
|
||||
console.log('SIWE validation result:', fields);
|
||||
|
||||
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]
|
||||
);
|
||||
// Обновляем сессию
|
||||
req.session.userId = userId;
|
||||
req.session.authenticated = true;
|
||||
req.session.authType = 'wallet';
|
||||
req.session.isAdmin = isAdmin;
|
||||
req.session.address = address.toLowerCase();
|
||||
|
||||
const userId = userResult.rows[0].id;
|
||||
const isAdmin = false; // Будет обновлено в createSession
|
||||
// Сохраняем сессию
|
||||
await new Promise((resolve, reject) => {
|
||||
req.session.save((err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Используем централизованный сервис
|
||||
await authService.createSession(req, {
|
||||
// Возвращаем успешный ответ
|
||||
return res.json({
|
||||
success: true,
|
||||
userId,
|
||||
address,
|
||||
isAdmin,
|
||||
authType: 'wallet',
|
||||
guestId: req.session.guestId
|
||||
authenticated: true
|
||||
});
|
||||
|
||||
res.json({
|
||||
authenticated: true,
|
||||
userId,
|
||||
address,
|
||||
isAdmin,
|
||||
authType: 'wallet'
|
||||
});
|
||||
} 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);
|
||||
// Проверка статуса аутентификации
|
||||
router.get('/check', async (req, res) => {
|
||||
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'
|
||||
});
|
||||
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;
|
||||
@@ -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');
|
||||
@@ -26,10 +26,31 @@ 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('Все сервисы успешно инициализированы');
|
||||
|
||||
@@ -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]
|
||||
);
|
||||
// Нормализуем адрес
|
||||
address = ethers.getAddress(address);
|
||||
|
||||
if (existingUser.rows.length > 0) {
|
||||
const userId = existingUser.rows[0].id;
|
||||
const isAdmin = await this.checkAdminRole(address);
|
||||
return { 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]);
|
||||
|
||||
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';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Создаем и экспортируем единственный экземпляр
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
let botInstance = null;
|
||||
const verificationCodes = new Map();
|
||||
|
||||
logger.info('TelegramBot service initialized');
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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"
|
||||
|
||||
78
frontend/src/composables/useAuth.js
Normal file
78
frontend/src/composables/useAuth.js
Normal 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
|
||||
};
|
||||
}
|
||||
@@ -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:`,
|
||||
address,
|
||||
"",
|
||||
statement,
|
||||
"",
|
||||
`URI: ${origin}`,
|
||||
"Version: 1",
|
||||
"Chain ID: 1",
|
||||
`Nonce: ${nonce}`,
|
||||
`Issued At: ${new Date().toISOString()}`,
|
||||
"Resources:",
|
||||
`- ${origin}/api/auth/verify`
|
||||
].join("\n");
|
||||
const statement = 'Sign in with Ethereum to the app.';
|
||||
|
||||
const siweMessage = new SiweMessage({
|
||||
domain,
|
||||
address,
|
||||
statement,
|
||||
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();
|
||||
console.log('Verification response:', verificationResponse.data);
|
||||
|
||||
return { address, signer };
|
||||
// Обновляем состояние аутентификации
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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,53 +55,36 @@
|
||||
<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 }}
|
||||
</div>
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user