Тестовый коммит после удаления husky

This commit is contained in:
2025-03-05 01:02:09 +03:00
parent 97ca5e4b64
commit 3157ad0cd9
118 changed files with 8177 additions and 8530 deletions

View File

@@ -1,7 +1,10 @@
const { ethers } = require('ethers');
require('dotenv').config();
const db = require('../db');
const contractArtifact = require('../artifacts/contracts/MyContract.sol/MyContract.json');
const contractABI = contractArtifact.abi;
const logger = require('./logger');
const { getContract } = require('./contracts');
// Проверяем наличие необходимых переменных окружения
if (!process.env.ACCESS_TOKEN_ADDRESS) {
@@ -18,11 +21,7 @@ let accessToken;
try {
const AccessTokenABI = require('../artifacts/contracts/AccessToken.sol/AccessToken.json').abi;
accessToken = new ethers.Contract(
process.env.ACCESS_TOKEN_ADDRESS,
AccessTokenABI,
provider
);
accessToken = new ethers.Contract(process.env.ACCESS_TOKEN_ADDRESS, AccessTokenABI, provider);
} catch (error) {
console.error('Ошибка инициализации контракта AccessToken:', error);
}
@@ -49,10 +48,10 @@ async function checkAccess(address) {
const roles = ['ADMIN', 'MODERATOR', 'SUPPORT'];
const role = roles[roleId];
return {
hasAccess: true,
return {
hasAccess: true,
role,
tokenId: activeTokenId.toString()
tokenId: activeTokenId.toString(),
};
} catch (error) {
console.error('Access check error:', error);
@@ -60,30 +59,138 @@ async function checkAccess(address) {
}
}
async function checkAdmin(address) {
// Функция для проверки, является ли пользователь администратором
async function checkIfAdmin(address) {
try {
console.log('Проверка прав администратора для адреса:', address);
// Проверяем, является ли пользователь администратором через смарт-контракт
const contract = new ethers.Contract(
process.env.CONTRACT_ADDRESS,
contractABI,
provider
);
console.log('Контракт инициализирован:', {
address: process.env.CONTRACT_ADDRESS,
provider: provider.connection.url
});
const isAdmin = await contract.isAdmin(address);
console.log('Результат проверки из контракта:', isAdmin);
// Проверяем в базе данных
const result = await db.query('SELECT is_admin FROM users WHERE address = $1', [address]);
if (result.rows.length === 0) {
console.log(`Пользователь с адресом ${address} не найден в базе данных`);
return false;
}
const isAdmin = result.rows[0].is_admin;
console.log(`Пользователь с адресом ${address} имеет статус администратора:`, isAdmin);
return isAdmin;
} catch (error) {
console.error('Ошибка при проверке прав администратора:', error);
// В случае ошибки возвращаем false вместо выброса исключения
return false;
}
}
module.exports = { checkAccess, checkAdmin };
/**
* Проверяет баланс токенов пользователя и обновляет его роль
* @param {string} address - Адрес кошелька пользователя
* @returns {Promise<boolean>} - Имеет ли пользователь права администратора
*/
async function checkTokenBalanceAndUpdateRole(address) {
try {
// Получение контракта токенов
const accessTokenContract = await getContract('AccessToken');
// Проверка баланса
const balance = await accessTokenContract.balanceOf(address);
// Минимальное количество токенов для прав администратора
const minTokens = ethers.utils.parseUnits(process.env.MIN_ADMIN_TOKENS || "1", 18);
const isAdmin = balance.gte(minTokens);
// Получение ID пользователя по адресу кошелька
const userResult = await db.query(`
SELECT u.id FROM users u
JOIN user_identities ui ON u.id = ui.user_id
WHERE ui.identity_type = 'wallet' AND ui.identity_value = $1
`, [address.toLowerCase()]);
if (userResult.rows.length > 0) {
const userId = userResult.rows[0].id;
// Получение ID роли
const roleResult = await db.query(
'SELECT id FROM roles WHERE name = $1',
[isAdmin ? 'admin' : 'user']
);
if (roleResult.rows.length > 0) {
const roleId = roleResult.rows[0].id;
// Обновление роли пользователя
await db.query(
'UPDATE users SET role_id = $1, last_token_check = NOW() WHERE id = $2',
[roleId, userId]
);
logger.info(`Updated user ${userId} role to ${isAdmin ? 'admin' : 'user'} based on token balance`);
}
}
return isAdmin;
} catch (error) {
logger.error(`Error checking token balance for ${address}: ${error.message}`);
return false;
}
}
/**
* Получает информацию о пользователе, включая его роль
* @param {number} userId - ID пользователя
* @returns {Promise<Object>} - Информация о пользователе
*/
async function getUserInfo(userId) {
try {
const result = await db.query(`
SELECT u.id, u.username, u.preferred_language, r.name as role
FROM users u
LEFT JOIN roles r ON u.role_id = r.id
WHERE u.id = $1
`, [userId]);
if (result.rows.length === 0) {
return null;
}
return result.rows[0];
} catch (error) {
logger.error(`Error getting user info for ${userId}: ${error.message}`);
return null;
}
}
/**
* Запускает проверку токенов для всех пользователей
*/
async function checkAllUsersTokens() {
try {
// Получение всех пользователей с кошельками
const walletUsers = await db.query(`
SELECT u.id, ui.identity_value as address
FROM users u
JOIN user_identities ui ON u.id = ui.user_id
WHERE ui.identity_type = 'wallet'
`);
logger.info(`Checking token balances for ${walletUsers.rows.length} users`);
for (const user of walletUsers.rows) {
// Проверка баланса токенов
const hasTokens = await checkTokenBalanceAndUpdateRole(user.address);
logger.info(`User ${user.id} with address ${user.address}: admin=${hasTokens}`);
}
} catch (error) {
logger.error(`Error checking token balances: ${error.message}`);
}
}
module.exports = {
checkAccess,
checkIfAdmin,
checkTokenBalanceAndUpdateRole,
getUserInfo,
checkAllUsersTokens
};

View File

@@ -2,13 +2,11 @@ const { SiweMessage } = require('siwe');
const { ethers } = require('ethers');
const AccessTokenABI = require('../artifacts/contracts/AccessToken.sol/AccessToken.json').abi;
require('dotenv').config();
const { pool } = require('../db');
const provider = new ethers.JsonRpcProvider(process.env.ETHEREUM_NETWORK_URL);
const accessToken = new ethers.Contract(
process.env.ACCESS_TOKEN_ADDRESS,
AccessTokenABI,
provider
);
// В ethers.js v6.x используется JsonRpcProvider напрямую
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const accessToken = new ethers.Contract(process.env.ACCESS_TOKEN_ADDRESS, AccessTokenABI, provider);
// Проверяем наличие адреса контракта
if (!process.env.ACCESS_TOKEN_ADDRESS) {
@@ -25,13 +23,13 @@ if (!process.env.ACCESS_TOKEN_ADDRESS) {
async function verifySignature(message, signature, address) {
try {
// Формируем сообщение для проверки
const domain = message.domain || window.location.host;
const domain = message.domain || 'localhost';
const statement = message.statement || 'Sign in with Ethereum to the app.';
const uri = message.uri || window.location.origin;
const uri = message.uri || 'http://localhost:8000';
const version = message.version || '1';
const chainId = message.chainId || '1';
const nonce = message.nonce;
const messageToVerify = `${domain} wants you to sign in with your Ethereum account:
${address}
@@ -42,10 +40,10 @@ Version: ${version}
Chain ID: ${chainId}
Nonce: ${nonce}
`;
// Восстанавливаем адрес из подписи
// В ethers.js v6.x используется verifyMessage напрямую
const recoveredAddress = ethers.verifyMessage(messageToVerify, signature);
return recoveredAddress.toLowerCase() === address.toLowerCase();
} catch (error) {
console.error('Signature verification error:', error);
@@ -62,23 +60,125 @@ async function verifyAndCheckAccess(message, signature, address) {
// Проверяем подпись
const verified = await verifySignature(message, signature, address);
if (!verified) {
return {
return {
verified: false,
access: { hasAccess: false }
access: { hasAccess: false },
};
}
// Проверяем доступ
const access = await checkAccess(address);
return {
verified: true,
access
access,
};
}
// Функция для поиска или создания пользователя
async function findOrCreateUser(identifier, identityType = 'wallet') {
try {
// Проверяем, является ли адрес адресом администратора
const isAdmin = identityType === 'wallet' &&
identifier.toLowerCase() === process.env.ADMIN_WALLET_ADDRESS.toLowerCase();
console.log(`Проверка на администратора: ${identifier.toLowerCase()} === ${process.env.ADMIN_WALLET_ADDRESS.toLowerCase()} = ${isAdmin}`);
// Проверяем, существует ли пользователь с таким идентификатором
const identityResult = await pool.query(
'SELECT user_id FROM user_identities WHERE identity_type = $1 AND identity_value = $2',
[identityType, identifier.toLowerCase()]
);
let userId;
let isNewUser = false;
if (identityResult.rows.length > 0) {
// Пользователь найден
userId = identityResult.rows[0].user_id;
console.log(`Найден существующий пользователь с ID: ${userId}`);
// Обновляем статус администратора, если это необходимо
if (isAdmin) {
await pool.query(
'UPDATE users SET is_admin = true WHERE id = $1',
[userId]
);
console.log(`Обновлен статус администратора для пользователя ${userId}`);
}
} else {
// Создаем нового пользователя с явным указанием всех необходимых полей
const username = `user_${Date.now()}`;
// Проверяем существование роли USER или ADMIN
const roleName = isAdmin ? 'ADMIN' : 'USER';
const roleCheck = await pool.query('SELECT id FROM roles WHERE name = $1', [roleName]);
let roleId;
if (roleCheck.rows.length > 0) {
roleId = roleCheck.rows[0].id;
} else {
// Если роли нет, создаем её
const newRole = await pool.query(
'INSERT INTO roles (name, description) VALUES ($1, $2) RETURNING id',
[roleName, isAdmin ? 'Administrator role' : 'Regular user role']
);
roleId = newRole.rows[0].id;
}
// Создаем пользователя с обязательными полями
const userResult = await pool.query(
`INSERT INTO users (username, role_id, address, created_at, is_admin)
VALUES ($1, $2, $3, NOW(), $4)
RETURNING id`,
[username, roleId, identifier.toLowerCase(), isAdmin]
);
userId = userResult.rows[0].id;
isNewUser = true;
// Создаем запись в таблице идентификаторов
await pool.query(
'INSERT INTO user_identities (user_id, identity_type, identity_value, verified, created_at) VALUES ($1, $2, $3, true, NOW())',
[userId, identityType, identifier.toLowerCase()]
);
console.log(`Создан новый пользователь с ID: ${userId}, isAdmin: ${isAdmin}`);
}
// Получаем информацию о пользователе
try {
const userInfo = await pool.query(
`SELECT u.id, u.username, u.is_admin, r.name as role
FROM users u
JOIN roles r ON u.role_id = r.id
WHERE u.id = $1`,
[userId]
);
if (userInfo.rows.length > 0) {
return userInfo.rows[0];
}
} catch (err) {
console.error('Ошибка при получении информации о пользователе:', err);
}
// Если не удалось получить полную информацию, возвращаем базовую
return {
id: userId,
username: isNewUser ? `user_${Date.now()}` : 'unknown',
is_admin: isAdmin,
role: isAdmin ? 'ADMIN' : 'USER'
};
} catch (error) {
console.error('Ошибка при поиске/создании пользователя:', error);
throw error;
}
}
module.exports = {
verifyAndCheckAccess,
verifySignature,
checkAccess
};
checkAccess,
findOrCreateUser
};

View File

@@ -14,4 +14,4 @@ async function checkMailServer(domain) {
}
}
module.exports = { checkMailServer };
module.exports = { checkMailServer };

View File

@@ -0,0 +1,46 @@
const { ethers } = require('ethers');
const fs = require('fs');
const path = require('path');
const logger = require('./logger');
/**
* Получает экземпляр контракта по его имени
* @param {string} contractName - Имя контракта (например, 'AccessToken')
* @returns {Promise<ethers.Contract>} - Экземпляр контракта
*/
async function getContract(contractName) {
try {
// Путь к артефакту контракта
const artifactPath = path.join(__dirname, '..', 'artifacts', 'contracts', `${contractName}.sol`, `${contractName}.json`);
// Проверка существования файла
if (!fs.existsSync(artifactPath)) {
throw new Error(`Артефакт контракта ${contractName} не найден по пути ${artifactPath}`);
}
// Загрузка ABI из артефакта
const contractArtifact = require(artifactPath);
const contractABI = contractArtifact.abi;
// Получение адреса контракта из переменных окружения
const contractAddress = process.env[`${contractName.toUpperCase()}_ADDRESS`];
if (!contractAddress) {
throw new Error(`Адрес контракта ${contractName} не найден в переменных окружения`);
}
// Подключение к провайдеру
const provider = new ethers.JsonRpcProvider(process.env.ETHEREUM_NETWORK_URL);
// Создание экземпляра контракта
const contract = new ethers.Contract(contractAddress, contractABI, provider);
return contract;
} catch (error) {
logger.error(`Ошибка при получении контракта ${contractName}: ${error.message}`);
throw error;
}
}
module.exports = {
getContract
};

2
backend/utils/db.js Normal file
View File

@@ -0,0 +1,2 @@
// Реэкспорт основного модуля db
module.exports = require('../db');

View File

@@ -1,6 +1,6 @@
// Функция для создания задержки
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
return new Promise((resolve) => setTimeout(resolve, ms));
}
// Функция для валидации email адреса
@@ -11,5 +11,5 @@ function isValidEmail(email) {
module.exports = {
sleep,
isValidEmail
};
isValidEmail,
};

View File

@@ -3,7 +3,7 @@ const { Pool } = require('pg');
// Подключение к БД
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false,
});
/**
@@ -20,7 +20,7 @@ async function linkIdentity(userId, identityType, identityValue) {
'SELECT * FROM user_identities WHERE identity_type = $1 AND identity_value = $2',
[identityType, identityValue]
);
if (existingResult.rows.length > 0) {
// Если идентификатор уже связан с другим пользователем, возвращаем ошибку
if (existingResult.rows[0].user_id !== userId) {
@@ -30,13 +30,13 @@ async function linkIdentity(userId, identityType, identityValue) {
// Если идентификатор уже связан с этим пользователем, ничего не делаем
return true;
}
// Добавляем новую связь
await pool.query(
'INSERT INTO user_identities (user_id, identity_type, identity_value, created_at) VALUES ($1, $2, $3, NOW())',
[userId, identityType, identityValue]
);
console.log(`Successfully linked ${identityType}:${identityValue} to user ${userId}`);
return true;
} catch (error) {
@@ -57,11 +57,11 @@ async function getUserIdByIdentity(identityType, identityValue) {
'SELECT user_id FROM user_identities WHERE identity_type = $1 AND identity_value = $2',
[identityType, identityValue]
);
if (result.rows.length === 0) {
return null;
}
return result.rows[0].user_id;
} catch (error) {
console.error('Error getting user ID by identity:', error);
@@ -80,7 +80,7 @@ async function getUserIdentities(userId) {
'SELECT identity_type, identity_value FROM user_identities WHERE user_id = $1',
[userId]
);
return result.rows;
} catch (error) {
console.error('Error getting user identities:', error);
@@ -91,5 +91,5 @@ async function getUserIdentities(userId) {
module.exports = {
linkIdentity,
getUserIdByIdentity,
getUserIdentities
};
getUserIdentities,
};

View File

@@ -3,25 +3,19 @@ const path = require('path');
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
format: winston.format.combine(winston.format.timestamp(), winston.format.json()),
transports: [
new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
)
format: winston.format.combine(winston.format.colorize(), winston.format.simple()),
}),
new winston.transports.File({
filename: path.join(__dirname, '../logs/error.log'),
level: 'error'
level: 'error',
}),
new winston.transports.File({
filename: path.join(__dirname, '../logs/combined.log')
})
]
filename: path.join(__dirname, '../logs/combined.log'),
}),
],
});
module.exports = logger;
module.exports = logger;

4
backend/utils/wallet.js Normal file
View File

@@ -0,0 +1,4 @@
import { ethers } from 'ethers';
const provider = new ethers.JsonRpcProvider(process.env.ETHEREUM_NETWORK_URL);
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider);