@@ -4,109 +4,37 @@ const { ethers } = require('ethers');
const crypto = require ( 'crypto' ) ;
const { processMessage } = require ( './ai-assistant' ) ; // Используем AI Assistant
const ADMIN _CONTRACTS = [
{ address : "0xd95a45fc46a7300e6022885afec3d618d7d3f27c" , network : "eth" } ,
{ address : "0x1d47f12ffA279BFE59Ab16d56fBb10d89AECdD5D" , network : "bsc" } ,
{ address : "0xdce769b847a0a697239777d0b1c7dd33b6012ba0" , network : "arbitrum" } ,
{ address : "0x351f59de4fedbdf7601f5592b93db3b9330c1c1d" , network : "polygon" }
] ;
const ERC20 _ABI = [
"function balanceOf(address owner) view returns (uint256)"
] ;
class AuthService {
constructor ( ) {
// Инициализация провайдеров для разных сетей
this . providers = {
eth : new ethers . JsonRpcProvider ( process . env . RPC _URL _ETH ) ,
polygon : new ethers . JsonRpcProvider ( process . env . RPC _URL _POLYGON ) ,
bsc : new ethers . JsonRpcProvider ( process . env . RPC _URL _BSC ) ,
arbitrum : new ethers . JsonRpcProvider ( process . env . RPC _URL _ARBITRUM )
} ;
// Конфигурация токенов для разных сетей
this . tokenContracts = [
{ address : "0xd95a45fc46a7300e6022885afec3d618d7d3f27c" , network : "eth" } , // Ethereum
{ address : "0x1d47f12ffA279BFE59Ab16d56fBb10d89AECdD5D" , network : "bsc" } , // Binance Smart Chain
{ address : "0xdce769b847a0a697239777d0b1c7dd33b6012ba0" , network : "arbitrum" } , // Arbitrum
{ address : "0x351f59de4fedbdf7601f5592b93db3b9330c1c1d" , network : "polygon" } // Polygon
] ;
this . MIN _BALANCE = ethers . parseUnits ( "1000000.0" , 18 ) ; // 1,000,000 токенов для роли админа
}
// Проверка подписи
async verifySignature ( message , signature , address ) {
try {
logger . info ( 'Verifying signature:' , {
message : message . substring ( 0 , 100 ) + '...' ,
signature : signature . substring ( 0 , 10 ) + '...' ,
address
} ) ;
if ( ! message || ! signature || ! address ) {
logger . error ( 'Missing parameters for signature verification' ) ;
return false ;
}
try {
// Восстанавливаем адрес из подписи через ethers
if ( ! message || ! signature || ! address ) return false ;
const recoveredAddress = ethers . verifyMessage ( message , signature ) ;
return ethers . getAddress ( recoveredAddress ) === ethers . getAddress ( address ) ;
} catch ( error ) {
logger . error ( 'Error in signature verification:' , error ) ;
return false ;
}
} catch ( error ) {
logger . error ( 'Error in verifySignature:' , error ) ;
return false ;
}
}
/**
* Проверяет наличие токенов на кошельке
* @param {string} walletAddress - Адрес кошелька
* @returns {Promise<boolean>} - Имеет ли кошелек токены
*/
async checkAdminTokens ( walletAddress ) {
try {
for ( const contract of this . tokenContracts ) {
try {
const provider = this . providers [ contract . network ] ;
const tokenContract = new ethers . Contract (
contract . address ,
[ 'function balanceOf(address) view returns (uint256)' ] ,
provider
) ;
const balance = await tokenContract . balanceOf ( walletAddress ) ;
logger . info ( ` Balance for ${ walletAddress } on ${ contract . network } : ${ balance } ` ) ;
if ( balance >= this . MIN _BALANCE ) {
logger . info ( ` Admin token found on ${ contract . network } for ${ walletAddress } ` ) ;
return true ;
}
} catch ( error ) {
logger . error ( ` Error checking balance on ${ contract . network } : ` , error ) ;
}
}
logger . info ( ` No admin tokens found for ${ walletAddress } ` ) ;
return false ;
} catch ( error ) {
logger . error ( 'Error in checkAdminTokens:' , error ) ;
return false ;
}
}
/**
* Проверяет баланс токенов и обновляет роль пользователя
* @param {string} address - Адрес кошелька
* @returns {Promise<boolean>} - Является ли пользователь админом
*/
async checkTokensAndUpdateRole ( address ) {
try {
const isAdmin = await this . checkAdminTokens ( address ) ;
// Обновляем роль в базе данных
await this . updateUserRole ( address , isAdmin ) ;
logger . info ( ` Updated role for user with address ${ address } : admin= ${ isAdmin } ` ) ;
return isAdmin ;
} catch ( error ) {
logger . error ( 'Error in checkTokensAndUpdateRole:' , error ) ;
return false ;
}
}
/**
@@ -117,8 +45,7 @@ class AuthService {
async findOrCreateUser ( address ) {
try {
const existingUser = await db . query (
` SELECT u.id,
(u.role = 'admin') as is_admin
` SELECT u.id
FROM users u
JOIN user_identities ui ON u.id = ui.user_id
WHERE ui.provider = 'wallet'
@@ -127,17 +54,18 @@ class AuthService {
) ;
if ( existingUser . rows . length > 0 ) {
return existingUser . rows [ 0 ] ;
const userId = existingUser . rows [ 0 ] . id ;
const isAdmin = await this . checkAdminRole ( address ) ;
return { userId , isAdmin } ;
}
// Если пользователь не найден, создаем нового
// Создание нового пользователя
const result = await db . query (
'INSERT INTO users DEFAULT VALUES RETURNING id' ,
[ ]
) ;
const userId = result . rows [ 0 ] . id ;
// Добавляем wallet identity
await db . query (
` INSERT INTO user_identities
(user_id, provider, provider_id, identity_type, identity_value)
@@ -145,13 +73,8 @@ class AuthService {
[ userId , address . toLowerCase ( ) ]
) ;
// Проверяем роль админа
const isAdmin = await this . checkAdminRole ( userId ) ;
return {
userId ,
isAdmin
} ;
const isAdmin = await this . checkAdminRole ( address ) ;
return { userId , isAdmin } ;
} catch ( error ) {
console . error ( 'Error in findOrCreateUser:' , error ) ;
throw error ;
@@ -159,342 +82,101 @@ class AuthService {
}
/**
* О б новляет роль пользователя и связанные данные
* О с новной метод проверки роли админа
* @param {string} address - Адрес кошелька
* @returns {Promise<boolean>} - Является ли пользователь админом
*/
async updateUser Role( address , isAdmin ) {
async checkAdmin Role( address ) {
if ( ! address ) return false ;
logger . info ( ` Checking admin role for address: ${ address } ` ) ;
let foundTokens = false ;
const balances = { } ;
for ( const contract of ADMIN _CONTRACTS ) {
try {
const result = await db . query ( `
UPDATE users u
SET
role = $ 2::user_role
FROM user_identities ui
WHERE u.id = ui.user_id
AND ui. provider = 'wallet'
AND LOWER(ui.provider_id) = LOWER( $ 1)
RETURNING u.id
` , [
const provider = this . providers [ contract . network ] ;
if ( ! provider ) continue ;
const tokenContract = new ethers . Contract (
contract . address ,
ERC20 _ABI ,
provider
) ;
const balance = await tokenContract . balanceOf ( address ) ;
const formattedBalance = ethers . formatUnits ( balance , 18 ) ;
balances [ contract . network ] = formattedBalance ;
logger . info ( ` Token balance on ${ contract . network } : ` , {
address ,
isAdmin ? 'admin' : 'user'
] ) ;
contract : contract . address ,
balance : formattedBalance ,
hasTokens : balance > 0
} ) ;
if ( result . rows . length > 0 ) {
logger . info ( ` Updated role for user ${ result . rows [ 0 ] . id } to ${ isAdmin ? 'admin' : 'user' } ` ) ;
if ( balance > 0 ) {
logger . info ( ` Found admin tokens on ${ contract . network } ` ) ;
foundTokens = true ;
}
} catch ( error ) {
logger . error ( ' Error updating user role:' , error ) ;
logger . error ( ` Error checking balance in ${ contract . network } : ` , {
address ,
contract : contract . address ,
error : error . message
} ) ;
balances [ contract . network ] = 'Error' ;
}
}
// Связывание идентификаторов (из identity-linker.js)
async linkIdentity ( userId , type , value ) {
try {
// Проверяем, не связан ли идентификатор с другим пользователем
const existingResult = await db . query (
` SELECT user_id
FROM user_identities
WHERE identity_type = $ 1
AND LOWER(identity_value) = LOWER( $ 2) ` ,
[ type , value ]
) ;
if ( existingResult . rows . length > 0 && existingResult . rows [ 0 ] . user _id !== userId ) {
throw new Error ( 'Identity already linked to another user' ) ;
}
// Добавляем или обновляем идентификатор
await db . query (
` INSERT INTO user_identities
(user_id, identity_type, identity_value, verified, created_at)
VALUES ( $ 1, $ 2, $ 3, true, NOW())
ON CONFLICT (identity_type, identity_value)
DO UPDATE SET user_id = $ 1, verified = true ` ,
[ userId , type , value ]
) ;
// Если это кошелек, проверяем права админа
if ( type === 'wallet' ) {
await this . checkTokensAndUpdateRole ( value ) ;
}
if ( foundTokens ) {
logger . info ( ` Admin role summary for ${ address } : ` , {
networks : Object . keys ( balances ) . filter ( net => balances [ net ] > 0 ) ,
balances
} ) ;
logger . info ( ` Admin role granted for ${ address } ` ) ;
return true ;
} catch ( error ) {
logger . error ( 'Error linking identity:' , error ) ;
throw error ;
}
}
// Получение всех идентификаторов пользователя
async getUserIdentities ( userId ) {
try {
const result = await db . query (
` SELECT identity_type, identity_value, verified, created_at
FROM user_identities
WHERE user_id = $ 1 ` ,
[ userId ]
) ;
return result . rows ;
} catch ( error ) {
logger . error ( 'Error getting user identities:' , error ) ;
return [ ] ;
}
}
// Проверка роли админа
async isAdmin ( userId ) {
try {
const result = await db . query (
'SELECT is_admin FROM users WHERE id = $1' ,
[ userId ]
) ;
return result . rows . length > 0 && result . rows [ 0 ] . is _admin ;
} catch ( error ) {
logger . error ( 'Error checking admin status:' , error ) ;
logger . info ( ` Admin role denied - no tokens found for ${ address } ` ) ;
return false ;
}
}
/**
* Получает все токены доступа
* @returns {Promise<Array>} - Список токенов доступа
*/
async getAllTokens ( ) {
try {
const result = await db . query ( `
SELECT * FROM access_tokens
ORDER BY created_at DESC
` ) ;
// Создание сессии с проверкой роли
async createSession ( req , { userId , address , authType , guestId } ) {
let isAdmin = false ;
return result . rows . map ( token => ( {
id : token . id ,
walletAddress : token . wallet _address ,
role : token . role ,
createdAt : token . created _at ,
expiresAt : token . expires _at ,
} ) ) ;
} catch ( error ) {
logger . error ( ` Error getting all tokens: ${ error . message } ` ) ;
return [ ] ;
if ( address ) {
isAdmin = await this . checkAdminRole ( address ) ;
} else if ( userId ) {
const linkedWallet = await this . getLinkedWallet ( userId ) ;
if ( linkedWallet ) {
isAdmin = await this . checkAdminRole ( linkedWallet ) ;
}
}
/**
* Получает ID пользователя по идентификатору
* @param {string} identityType - Тип идентификатора ('wallet', 'email', 'telegram')
* @param {string} identityValue - Значение идентификатора
* @returns {Promise<number|null>} - ID пользователя или null, если пользователь не найден
*/
async getUserIdByIdentity ( identityType , identityValue ) {
try {
// Нормализуем значение идентификатора
const normalizedValue = identityType === 'wallet'
? identityValue . toLowerCase ( )
: identityValue ;
// Получаем ID пользователя
const result = await db . query ( `
SELECT u.id FROM users u
JOIN user_identities ui ON u.id = ui.user_id
WHERE ui.identity_type = $ 1 AND LOWER(ui.identity_value) = LOWER( $ 2)
` , [ identityType , normalizedValue ] ) ;
if ( result . rows . length === 0 ) {
return null ;
}
return result . rows [ 0 ] . id ;
} catch ( error ) {
logger . error ( ` Ошибка при получении ID пользователя по идентификатору: ${ error . message } ` ) ;
return null ;
}
}
/**
* Обрабатывает гостевые сообщения после аутентификации
*/
async processGuestMessages ( userId , guestId ) {
try {
logger . info ( ` Processing guest messages for user ${ userId } with guestId ${ guestId } ` ) ;
// Сначала обновляем user_id для всех бесед с гостевыми сообщениями
await db . query (
` UPDATE conversations c
SET user_id = $ 1
WHERE id IN (
SELECT DISTINCT conversation_id
FROM messages m
WHERE m.metadata->>'guest_id' = $ 2
) ` ,
[ userId , guestId ]
) ;
// Получаем все гостевые сообщения без ответов
const guestMessages = await db . query (
` SELECT m.id, m.content, m.conversation_id, m.metadata, m.created_at
FROM messages m
WHERE m.metadata->>'guest_id' = $ 1
AND NOT EXISTS (
SELECT 1 FROM messages
WHERE conversation_id = m.conversation_id
AND sender_type = 'assistant'
)
ORDER BY m.created_at ASC ` ,
[ guestId ]
) ;
logger . info ( ` Found ${ guestMessages . rows . length } unprocessed guest messages ` ) ;
// Обрабатываем каждое гостевое сообщение
for ( const msg of guestMessages . rows ) {
logger . info ( ` Processing guest message ${ msg . id } : ${ msg . content } ` ) ;
// Получаем язык из метаданных
const metadata = typeof msg . metadata === 'string' ? JSON . parse ( msg . metadata ) : msg . metadata ;
const language = metadata ? . language || 'ru' ;
// Используем AI Assistant для обработки сообщения
const aiResponse = await processMessage ( userId , msg . content , language ) ;
// Сохраняем ответ AI в ту же беседу
await db . query (
` INSERT INTO messages
(conversation_id, sender_type, content, channel, created_at)
VALUES ( $ 1, 'assistant', $ 2, 'chat', NOW()) ` ,
[ msg . conversation _id , aiResponse ]
) ;
logger . info ( ` Saved AI response for message ${ msg . id } ` ) ;
}
// Обновляем метаданные сообщений, чтобы показать, что они обработаны
await db . query (
` UPDATE messages m
SET metadata = jsonb_set(
CASE
WHEN m.metadata IS NULL THEN '{}'::jsonb
ELSE m.metadata::jsonb
END,
'{processed}',
'true'
)
WHERE m.metadata->>'guest_id' = $ 1 ` ,
[ guestId ]
) ;
logger . info ( ` Successfully processed all guest messages for user ${ userId } ` ) ;
return true ;
} catch ( error ) {
logger . error ( 'Error processing guest messages:' , error ) ;
return false ;
}
}
async disconnect ( ) {
try {
// Очищаем состояние аутентификации
this . isAuthenticated = false ;
this . userId = null ;
this . address = null ;
this . isAdmin = false ;
this . authType = null ;
// Очищаем сессию
localStorage . removeItem ( 'auth' ) ;
// Очищаем guestId
localStorage . removeItem ( 'guestId' ) ;
return true ;
} catch ( error ) {
logger . error ( 'Error during disconnect:' , error ) ;
return false ;
}
}
async createSession ( req , userData ) {
// Сохраняем существующие данные сессии
const existingData = { ... req . session } ;
req . session . userId = userData . userId ;
req . session . address = userData . address ;
req . session . isAdmin = userData . isAdmin ;
req . session . userId = userId ;
req . session . address = address ;
req . session . isAdmin = isAdmin ;
req . session . authenticated = true ;
req . session . authType = userData . authType;
req . session . authType = authType ;
// Если есть гостевые сообщения в существующей сессии
if ( existingData . guestId ) {
req . session . guestId = existingData . guestId ;
// Связываем сообщения сразу здесь
await this . linkGuestMessages ( req , {
userId : userData . userId ,
guestId : existingData . guestId
} ) ;
if ( guestId ) {
req . session . guestId = guestId ;
}
return new Promise ( ( r esolve , reject ) => {
req . session . save ( ( err ) => {
if ( err ) reject ( err ) ;
else resolve ( ) ;
} ) ;
} ) ;
await req . s ession . save ( ) ;
}
async linkGuestMessages ( req , userData ) {
if ( ! userData . guest Id) return ;
const { rows } = await db . query (
'SELECT EXISTS(SELECT 1 FROM guest_messages WHERE guest_id = $1)' ,
[ userData . guestId ]
) ;
if ( rows [ 0 ] . exists ) {
// Сначала связываем сообщения
await db . query ( 'SELECT link_guest_messages($1, $2)' ,
[ userData . userId , userData . guestId ]
) ;
// Только после успешного связывания удаляем guestId
delete req . session . guestId ;
}
}
async checkAdminRole ( userId ) {
try {
// Получаем все идентификаторы пользователя
const identities = await db . query (
` SELECT provider, provider_id
// Получение связанного кошелька
async getLinkedWallet ( user Id) {
const result = await db . query (
` SELECT identity_value as address
FROM user_identities
WHERE user_id = $ 1 ` ,
WHERE user_id = $ 1 AND identity_type = 'wallet' ` ,
[ userId ]
) ;
// Ищем wallet среди идентификаторов
const wallet = identities . rows . find ( i => i . provider === 'wallet' ) ;
if ( ! wallet ) return false ;
// Проверяем баланс токенов
const hasTokens = await this . checkAdminTokens ( wallet . provider _id ) ;
if ( ! hasTokens ) return false ;
// Обновляем роль пользователя
await db . query (
` UPDATE users SET role = 'admin' WHERE id = $ 1 ` ,
[ userId ]
) ;
return true ;
} catch ( error ) {
console . error ( 'Error checking admin role:' , error ) ;
return false ;
}
}
// Проверка при каждой аутентификации
async verifyIdentity ( type , value ) {
const userId = await this . getUserIdByIdentity ( type , value ) ;
if ( ! userId ) return false ;
// Проверяем роль только если есть связанный кошелек
await this . checkAdminRole ( userId ) ;
return true ;
return result . rows [ 0 ] ? . address ;
}
}