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

This commit is contained in:
2025-03-03 20:43:58 +03:00
parent 13e240f871
commit d36fc255d8
56 changed files with 4451 additions and 3390 deletions

View File

@@ -0,0 +1,89 @@
const { ethers } = require('ethers');
require('dotenv').config();
const contractArtifact = require('../artifacts/contracts/MyContract.sol/MyContract.json');
const contractABI = contractArtifact.abi;
// Проверяем наличие необходимых переменных окружения
if (!process.env.ACCESS_TOKEN_ADDRESS) {
console.error('ACCESS_TOKEN_ADDRESS не указан в .env файле');
}
if (!process.env.ETHEREUM_NETWORK_URL) {
console.error('ETHEREUM_NETWORK_URL не указан в .env файле');
}
// Подключение к контракту
const provider = new ethers.JsonRpcProvider(process.env.ETHEREUM_NETWORK_URL);
let accessToken;
try {
const AccessTokenABI = require('../artifacts/contracts/AccessToken.sol/AccessToken.json').abi;
accessToken = new ethers.Contract(
process.env.ACCESS_TOKEN_ADDRESS,
AccessTokenABI,
provider
);
} catch (error) {
console.error('Ошибка инициализации контракта AccessToken:', error);
}
/**
* Проверяет доступ и роль пользователя
* @param {string} address - Ethereum адрес пользователя
* @returns {Promise<{hasAccess: boolean, role: string|null}>}
*/
async function checkAccess(address) {
try {
if (!address || !accessToken) {
return { hasAccess: false, role: null };
}
// Проверяем активный токен
const activeTokenId = await accessToken.activeTokens(address);
if (activeTokenId.toString() === '0') {
return { hasAccess: false, role: null };
}
// Получаем роль
const roleId = await accessToken.checkRole(address);
const roles = ['ADMIN', 'MODERATOR', 'SUPPORT'];
const role = roles[roleId];
return {
hasAccess: true,
role,
tokenId: activeTokenId.toString()
};
} catch (error) {
console.error('Access check error:', error);
return { hasAccess: false, role: null };
}
}
async function checkAdmin(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);
return isAdmin;
} catch (error) {
console.error('Ошибка при проверке прав администратора:', error);
// В случае ошибки возвращаем false вместо выброса исключения
return false;
}
}
module.exports = { checkAccess, checkAdmin };

84
backend/utils/auth.js Normal file
View File

@@ -0,0 +1,84 @@
const { SiweMessage } = require('siwe');
const { ethers } = require('ethers');
const AccessTokenABI = require('../artifacts/contracts/AccessToken.sol/AccessToken.json').abi;
require('dotenv').config();
const provider = new ethers.JsonRpcProvider(process.env.ETHEREUM_NETWORK_URL);
const accessToken = new ethers.Contract(
process.env.ACCESS_TOKEN_ADDRESS,
AccessTokenABI,
provider
);
// Проверяем наличие адреса контракта
if (!process.env.ACCESS_TOKEN_ADDRESS) {
console.error('ACCESS_TOKEN_ADDRESS не указан в .env файле');
}
/**
* Проверяет подпись сообщения
* @param {Object} message - Сообщение для проверки
* @param {string} signature - Подпись сообщения
* @param {string} address - Адрес кошелька
* @returns {Promise<boolean>} - Результат проверки
*/
async function verifySignature(message, signature, address) {
try {
// Формируем сообщение для проверки
const domain = message.domain || window.location.host;
const statement = message.statement || 'Sign in with Ethereum to the app.';
const uri = message.uri || window.location.origin;
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}
${statement}
URI: ${uri}
Version: ${version}
Chain ID: ${chainId}
Nonce: ${nonce}
`;
// Восстанавливаем адрес из подписи
const recoveredAddress = ethers.verifyMessage(messageToVerify, signature);
return recoveredAddress.toLowerCase() === address.toLowerCase();
} catch (error) {
console.error('Signature verification error:', error);
return false;
}
}
async function checkAccess(address) {
// Временная заглушка
return { hasAccess: false, role: null };
}
async function verifyAndCheckAccess(message, signature, address) {
// Проверяем подпись
const verified = await verifySignature(message, signature, address);
if (!verified) {
return {
verified: false,
access: { hasAccess: false }
};
}
// Проверяем доступ
const access = await checkAccess(address);
return {
verified: true,
access
};
}
module.exports = {
verifyAndCheckAccess,
verifySignature,
checkAccess
};

View File

@@ -0,0 +1,95 @@
const { Pool } = require('pg');
// Подключение к БД
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false
});
/**
* Связывает идентификатор с пользователем
* @param {number} userId - ID пользователя
* @param {string} identityType - Тип идентификатора ('ethereum', 'telegram', 'email')
* @param {string} identityValue - Значение идентификатора
* @returns {Promise<boolean>} - Результат операции
*/
async function linkIdentity(userId, identityType, identityValue) {
try {
// Проверяем, существует ли уже такой идентификатор
const existingResult = await pool.query(
'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) {
console.warn(`Identity ${identityType}:${identityValue} already linked to another user`);
return false;
}
// Если идентификатор уже связан с этим пользователем, ничего не делаем
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) {
console.error('Error linking identity:', error);
return false;
}
}
/**
* Получает ID пользователя по идентификатору
* @param {string} identityType - Тип идентификатора ('ethereum', 'telegram', 'email')
* @param {string} identityValue - Значение идентификатора
* @returns {Promise<number|null>} - ID пользователя или null, если не найден
*/
async function getUserIdByIdentity(identityType, identityValue) {
try {
const result = await pool.query(
'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);
return null;
}
}
/**
* Получает все идентификаторы пользователя
* @param {number} userId - ID пользователя
* @returns {Promise<Array|null>} - Массив идентификаторов или null в случае ошибки
*/
async function getUserIdentities(userId) {
try {
const result = await pool.query(
'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);
return null;
}
}
module.exports = {
linkIdentity,
getUserIdByIdentity,
getUserIdentities
};

27
backend/utils/logger.js Normal file
View File

@@ -0,0 +1,27 @@
const winston = require('winston');
const path = require('path');
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
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()
)
}),
new winston.transports.File({
filename: path.join(__dirname, '../logs/error.log'),
level: 'error'
}),
new winston.transports.File({
filename: path.join(__dirname, '../logs/combined.log')
})
]
});
module.exports = logger;