Описание изменений
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);
|
||||
|
||||
// Проверяем подпись через 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;
|
||||
@@ -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);
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Создаем и экспортируем единственный экземпляр
|
||||
|
||||
@@ -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();
|
||||
|
||||
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
|
||||
};
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user