Files
securebit-chat/src/token-auth/Web3ContractManager.js
lockbitchat e7c6dfc3b3 feat: Implement comprehensive token-based authentication system
Add complete Web3-powered token authentication module for SecureBit project

- **TokenAuthManager.js**: Main authentication manager handling Web3 wallet connections,
  session creation/validation, and automatic session termination
- **Web3ContractManager.js**: Smart contract interface for token operations and validation
- **SecureBitAccessToken.sol**: ERC-721 smart contract for access tokens with monthly/yearly durations

- **TokenAuthModal.jsx**: User interface for wallet connection and token purchase
- **TokenStatus.jsx**: Header component displaying token status and remaining time

- ERC-721 compliant access tokens with configurable durations (1 month/1 year)
- OpenZeppelin security contracts integration (Ownable, ReentrancyGuard, Pausable)
- Token purchase, renewal, and deactivation functionality
- Automatic expiry validation and price management
- Transfer handling with user token tracking
- Pausable functionality for emergency contract control

- `purchaseMonthlyToken()` / `purchaseYearlyToken()`: Token acquisition
- `isTokenValid()`: Real-time token validation
- `renewToken()`: Token extension functionality
- `deactivateToken()`: Manual token deactivation
- `getTokenPrices()`: Dynamic pricing information
- `pause()` / `unpause()`: Emergency control functions

- Web3 signature verification for wallet ownership
- Single active session enforcement per account
- Automatic session termination on new device login
- Cryptographic signature validation
- MITM and replay attack protection preservation
- Blockchain-based token validation

- Modular architecture for easy integration
- Web3.js integration for Ethereum network interaction
- MetaMask wallet support
- Session heartbeat monitoring
- Automatic token expiry handling
- Comprehensive error handling and logging

- src/token-auth/TokenAuthManager.js
- src/token-auth/Web3ContractManager.js
- src/token-auth/SecureBitAccessToken.sol
- src/token-auth/config.js
- src/components/ui/TokenAuthModal.jsx
- src/components/ui/TokenStatus.jsx

- Smart contract includes comprehensive test scenarios
- Mock mode available for development testing
- Hardhat deployment scripts provided
2025-08-24 23:56:12 -04:00

622 lines
22 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ============================================
// WEB3 CONTRACT MANAGER
// ============================================
// Управление смарт-контрактом токенов доступа
// Интеграция с MetaMask и другими Web3 провайдерами
// ============================================
class Web3ContractManager {
constructor() {
this.contract = null;
this.web3 = null;
this.contractAddress = null;
this.contractABI = null;
this.isInitialized = false;
// Адреса контрактов для разных сетей
this.CONTRACT_ADDRESSES = {
// Mainnet
'0x1': '0x0000000000000000000000000000000000000000', // Заменить на реальный адрес
// Ropsten (тестовая сеть)
'0x3': '0x0000000000000000000000000000000000000000', // Заменить на реальный адрес
// Goerli (тестовая сеть)
'0x5': '0x0000000000000000000000000000000000000000', // Заменить на реальный адрес
// Sepolia (тестовая сеть)
'0xaa36a7': '0x0000000000000000000000000000000000000000' // Заменить на реальный адрес
};
// ABI контракта (упрощенная версия)
this.CONTRACT_ABI = [
// События
{
"anonymous": false,
"inputs": [
{"indexed": true, "name": "tokenId", "type": "uint256"},
{"indexed": true, "name": "owner", "type": "address"},
{"indexed": false, "name": "tokenType", "type": "uint8"},
{"indexed": false, "name": "expiryDate", "type": "uint256"}
],
"name": "TokenMinted",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{"indexed": true, "name": "tokenId", "type": "uint256"},
{"indexed": true, "name": "owner", "type": "address"}
],
"name": "TokenExpired",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{"indexed": true, "name": "tokenId", "type": "uint256"},
{"indexed": false, "name": "newExpiryDate", "type": "uint256"}
],
"name": "TokenRenewed",
"type": "event"
},
// Функции чтения
{
"inputs": [{"name": "tokenId", "type": "uint256"}],
"name": "isTokenValid",
"outputs": [{"name": "", "type": "bool"}],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [{"name": "tokenId", "type": "uint256"}],
"name": "getTokenInfo",
"outputs": [
{"name": "tokenId", "type": "uint256"},
{"name": "owner", "type": "address"},
{"name": "expiryDate", "type": "uint256"},
{"name": "tokenType", "type": "uint8"},
{"name": "isActive", "type": "bool"},
{"name": "createdAt", "type": "uint256"},
{"name": "metadata", "type": "string"}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [{"name": "user", "type": "address"}],
"name": "getUserTokens",
"outputs": [{"name": "", "type": "uint256[]"}],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [{"name": "user", "type": "address"}],
"name": "getActiveUserTokens",
"outputs": [{"name": "", "type": "uint256[]"}],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [{"name": "user", "type": "address"}],
"name": "hasActiveToken",
"outputs": [{"name": "", "type": "bool"}],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "monthlyPrice",
"outputs": [{"name": "", "type": "uint256"}],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "yearlyPrice",
"outputs": [{"name": "", "type": "uint256"}],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getStats",
"outputs": [
{"name": "totalTokens", "type": "uint256"},
{"name": "activeTokens", "type": "uint256"},
{"name": "monthlyTokens", "type": "uint256"},
{"name": "yearlyTokens", "type": "uint256"}
],
"stateMutability": "view",
"type": "function"
},
// Функции записи
{
"inputs": [],
"name": "purchaseMonthlyToken",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "purchaseYearlyToken",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [{"name": "tokenId", "type": "uint256"}],
"name": "deactivateToken",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [{"name": "tokenId", "type": "uint256"}],
"name": "renewToken",
"outputs": [],
"stateMutability": "payable",
"type": "function"
}
];
this.initialize();
}
// Инициализация Web3 и контракта
async initialize() {
try {
// Проверяем поддержку Web3
if (typeof window.ethereum !== 'undefined') {
console.log('✅ Web3 detected');
// Создаем Web3 экземпляр
this.web3 = new Web3(window.ethereum);
// Получаем текущую сеть
const chainId = await this.getCurrentChainId();
console.log('🔗 Current chain ID:', chainId);
// Получаем адрес контракта для текущей сети
this.contractAddress = this.CONTRACT_ADDRESSES[chainId];
if (!this.contractAddress || this.contractAddress === '0x0000000000000000000000000000000000000000') {
console.warn('⚠️ Contract not deployed on current network:', chainId);
this.showContractNotDeployedMessage(chainId);
return;
}
// Создаем экземпляр контракта
this.contract = new this.web3.eth.Contract(
this.CONTRACT_ABI,
this.contractAddress
);
console.log('📋 Contract initialized:', this.contractAddress);
this.isInitialized = true;
} else {
throw new Error('Web3 not detected');
}
} catch (error) {
console.error('❌ Web3ContractManager initialization failed:', error);
throw error;
}
}
// Получение текущего Chain ID
async getCurrentChainId() {
try {
const chainId = await window.ethereum.request({ method: 'eth_chainId' });
return chainId;
} catch (error) {
console.error('❌ Failed to get chain ID:', error);
throw error;
}
}
// Проверка валидности токена
async isTokenValid(tokenId) {
try {
if (!this.isInitialized || !this.contract) {
throw new Error('Contract not initialized');
}
const result = await this.contract.methods.isTokenValid(tokenId).call();
console.log('🔍 Token validation result:', { tokenId, isValid: result });
return result;
} catch (error) {
console.error('❌ Token validation failed:', error);
return false;
}
}
// Получение информации о токене
async getTokenInfo(tokenId) {
try {
if (!this.isInitialized || !this.contract) {
throw new Error('Contract not initialized');
}
const result = await this.contract.methods.getTokenInfo(tokenId).call();
// Преобразуем результат в удобный формат
const tokenInfo = {
tokenId: result.tokenId,
owner: result.owner,
expiryDate: parseInt(result.expiryDate),
tokenType: parseInt(result.tokenType),
isActive: result.isActive,
createdAt: parseInt(result.createdAt),
metadata: result.metadata
};
console.log('📋 Token info retrieved:', tokenInfo);
return tokenInfo;
} catch (error) {
console.error('❌ Failed to get token info:', error);
throw error;
}
}
// Получение токенов пользователя
async getUserTokens(userAddress) {
try {
if (!this.isInitialized || !this.contract) {
throw new Error('Contract not initialized');
}
const result = await this.contract.methods.getUserTokens(userAddress).call();
const tokenIds = result.map(id => parseInt(id));
console.log('👤 User tokens retrieved:', { user: userAddress, tokens: tokenIds });
return tokenIds;
} catch (error) {
console.error('❌ Failed to get user tokens:', error);
throw error;
}
}
// Получение активных токенов пользователя
async getActiveUserTokens(userAddress) {
try {
if (!this.isInitialized || !this.contract) {
throw new Error('Contract not initialized');
}
const result = await this.contract.methods.getActiveUserTokens(userAddress).call();
const tokenIds = result.map(id => parseInt(id));
console.log('✅ Active user tokens retrieved:', { user: userAddress, activeTokens: tokenIds });
return tokenIds;
} catch (error) {
console.error('❌ Failed to get active user tokens:', error);
throw error;
}
}
// Проверка наличия активного токена у пользователя
async hasActiveToken(userAddress) {
try {
if (!this.isInitialized || !this.contract) {
throw new Error('Contract not initialized');
}
const result = await this.contract.methods.hasActiveToken(userAddress).call();
console.log('🔍 Active token check:', { user: userAddress, hasActive: result });
return result;
} catch (error) {
console.error('❌ Failed to check active token:', error);
return false;
}
}
// Получение цен токенов
async getTokenPrices() {
try {
if (!this.isInitialized || !this.contract) {
throw new Error('Contract not initialized');
}
const [monthlyPrice, yearlyPrice] = await Promise.all([
this.contract.methods.monthlyPrice().call(),
this.contract.methods.yearlyPrice().call()
]);
const prices = {
monthly: this.web3.utils.fromWei(monthlyPrice, 'ether'),
yearly: this.web3.utils.fromWei(yearlyPrice, 'ether'),
monthlyWei: monthlyPrice,
yearlyWei: yearlyPrice
};
console.log('💰 Token prices retrieved:', prices);
return prices;
} catch (error) {
console.error('❌ Failed to get token prices:', error);
throw error;
}
}
// Получение статистики контракта
async getContractStats() {
try {
if (!this.isInitialized || !this.contract) {
throw new Error('Contract not initialized');
}
const result = await this.contract.methods.getStats().call();
const stats = {
totalTokens: parseInt(result.totalTokens),
activeTokens: parseInt(result.activeTokens),
monthlyTokens: parseInt(result.monthlyTokens),
yearlyTokens: parseInt(result.yearlyTokens)
};
console.log('📊 Contract stats retrieved:', stats);
return stats;
} catch (error) {
console.error('❌ Failed to get contract stats:', error);
throw error;
}
}
// Покупка месячного токена
async purchaseMonthlyToken(price) {
try {
if (!this.isInitialized || !this.contract) {
throw new Error('Contract not initialized');
}
const accounts = await this.web3.eth.getAccounts();
const userAddress = accounts[0];
console.log('🛒 Purchasing monthly token:', { user: userAddress, price: price });
const result = await this.contract.methods.purchaseMonthlyToken().send({
from: userAddress,
value: price
});
console.log('✅ Monthly token purchased:', result);
return result;
} catch (error) {
console.error('❌ Monthly token purchase failed:', error);
throw error;
}
}
// Покупка годового токена
async purchaseYearlyToken(price) {
try {
if (!this.isInitialized || !this.contract) {
throw new Error('Contract not initialized');
}
const accounts = await this.web3.eth.getAccounts();
const userAddress = accounts[0];
console.log('🛒 Purchasing yearly token:', { user: userAddress, price: price });
const result = await this.contract.methods.purchaseYearlyToken().send({
from: userAddress,
value: price
});
console.log('✅ Yearly token purchased:', result);
return result;
} catch (error) {
console.error('❌ Yearly token purchase failed:', error);
throw error;
}
}
// Деактивация токена
async deactivateToken(tokenId) {
try {
if (!this.isInitialized || !this.contract) {
throw new Error('Contract not initialized');
}
const accounts = await this.web3.eth.getAccounts();
const userAddress = accounts[0];
console.log('🚫 Deactivating token:', { tokenId, user: userAddress });
const result = await this.contract.methods.deactivateToken(tokenId).send({
from: userAddress
});
console.log('✅ Token deactivated:', result);
return result;
} catch (error) {
console.error('❌ Token deactivation failed:', error);
throw error;
}
}
// Продление токена
async renewToken(tokenId, price) {
try {
if (!this.isInitialized || !this.contract) {
throw new Error('Contract not initialized');
}
const accounts = await this.web3.eth.getAccounts();
const userAddress = accounts[0];
console.log('🔄 Renewing token:', { tokenId, user: userAddress, price: price });
const result = await this.contract.methods.renewToken(tokenId).send({
from: userAddress,
value: price
});
console.log('✅ Token renewed:', result);
return result;
} catch (error) {
console.error('❌ Token renewal failed:', error);
throw error;
}
}
// Получение событий о создании токенов
async getTokenMintedEvents(fromBlock = 0, toBlock = 'latest') {
try {
if (!this.isInitialized || !this.contract) {
throw new Error('Contract not initialized');
}
const events = await this.contract.getPastEvents('TokenMinted', {
fromBlock: fromBlock,
toBlock: toBlock
});
console.log('📝 Token minted events retrieved:', events.length);
return events;
} catch (error) {
console.error('❌ Failed to get token minted events:', error);
throw error;
}
}
// Получение событий о продлении токенов
async getTokenRenewedEvents(fromBlock = 0, toBlock = 'latest') {
try {
if (!this.isInitialized || !this.contract) {
throw new Error('Contract not initialized');
}
const events = await this.contract.getPastEvents('TokenRenewed', {
fromBlock: fromBlock,
toBlock: toBlock
});
console.log('🔄 Token renewed events retrieved:', events.length);
return events;
} catch (error) {
console.error('❌ Failed to get token renewed events:', error);
throw error;
}
}
// Проверка поддержки сети
isNetworkSupported(chainId) {
return this.CONTRACT_ADDRESSES.hasOwnProperty(chainId);
}
// Получение поддерживаемых сетей
getSupportedNetworks() {
return Object.keys(this.CONTRACT_ADDRESSES).map(chainId => ({
chainId: chainId,
name: this.getNetworkName(chainId),
contractAddress: this.CONTRACT_ADDRESSES[chainId]
}));
}
// Получение названия сети
getNetworkName(chainId) {
const networkNames = {
'0x1': 'Ethereum Mainnet',
'0x3': 'Ropsten Testnet',
'0x5': 'Goerli Testnet',
'0xaa36a7': 'Sepolia Testnet'
};
return networkNames[chainId] || 'Unknown Network';
}
// Переключение на поддерживаемую сеть
async switchToNetwork(chainId) {
try {
if (!this.isNetworkSupported(chainId)) {
throw new Error(`Network ${chainId} is not supported`);
}
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: chainId }]
});
console.log('🔄 Switched to network:', chainId);
} catch (error) {
console.error('❌ Failed to switch network:', error);
throw error;
}
}
// Добавление новой сети
async addNetwork(chainId, networkName, rpcUrl, blockExplorerUrl) {
try {
await window.ethereum.request({
method: 'wallet_addEthereumChain',
params: [{
chainId: chainId,
chainName: networkName,
rpcUrls: [rpcUrl],
blockExplorerUrls: [blockExplorerUrl],
nativeCurrency: {
name: 'Ether',
symbol: 'ETH',
decimals: 18
}
}]
});
console.log(' Network added:', { chainId, name: networkName });
} catch (error) {
console.error('❌ Failed to add network:', error);
throw error;
}
}
// Показ сообщений пользователю
showContractNotDeployedMessage(chainId) {
const message = `Smart contract not deployed on network ${chainId}. Please switch to a supported network or deploy the contract.`;
console.warn('⚠️', message);
// Здесь можно добавить UI уведомление
}
// Получение статуса инициализации
getInitializationStatus() {
return {
isInitialized: this.isInitialized,
web3: !!this.web3,
contract: !!this.contract,
contractAddress: this.contractAddress,
currentChainId: this.web3 ? this.web3.currentProvider.chainId : null
};
}
// Очистка ресурсов
destroy() {
this.contract = null;
this.web3 = null;
this.contractAddress = null;
this.isInitialized = false;
console.log('🗑️ Web3ContractManager destroyed');
}
}
export { Web3ContractManager };