Major Security Improvements:

- Enhanced user fingerprinting with WebGL, Canvas, and Audio fingerprinting
- Hardware binding to prevent F5/Ctrl+F5 abuse
- Persistent storage across browser sessions (localStorage + sessionStorage)
- Global demo session counter with 10 session limit per device
- Multi-tab protection (max 2 tabs simultaneously)
- Anti-reset protection with hardware mismatch detection

Demo Session Protection:
- Advanced fingerprint generation with CPU benchmarking
- Enhanced validation with cryptographic verification
- Automatic cleanup and session completion tracking
- Cooldown periods between sessions (1min + 15min completion)
- Weekly partial reset of global counters

Fixes:
- Fixed SessionTimer console spam after connection disconnect
- Added missing registerEnhancedDemoSessionUsage method
- Corrected method calls from generateUserFingerprint to generateAdvancedUserFingerprint
- Implemented proper event handling for connection state changes

WebRTC Improvements:
- Added peer-disconnect, new-connection, and connection-cleaned events
- Enhanced connection cleanup with proper UI notifications
- Fixed SessionTimer state management during disconnections
- Prevented infinite re-rendering and console logging

Performance Optimizations:
- Auto-save persistent data every 30 seconds
- Periodic cleanup of old session data (every 6 hours)
- Memory management for used preimages (10k limit)
- Tab heartbeat system for multi-tab detection

Testing:
- Demo sessions now properly enforce limits
- P2P anonymity maintained (no server validation)
- Compatible with incognito mode restrictions
- Resistant to common abuse techniques
This commit is contained in:
lockbitchat
2025-08-16 20:58:42 -04:00
parent 32635839c6
commit e4273f5150
5 changed files with 697 additions and 136 deletions

View File

@@ -3244,7 +3244,6 @@
}
try {
console.log('📤 Attempting to send message:', messageInput.substring(0, 100));
// Add the message to local messages immediately (sent message)
const sentMessage = {

View File

@@ -163,7 +163,7 @@ const EnhancedMinimalHeader = ({
React.createElement('p', {
key: 'subtitle',
className: 'text-xs sm:text-sm text-muted hidden sm:block'
}, 'End-to-end freedom. v4.0.02.88')
}, 'End-to-end freedom. v4.0.03.00')
])
]),

View File

@@ -2,12 +2,17 @@ const SessionTimer = ({ timeLeft, sessionType, sessionManager }) => {
const [currentTime, setCurrentTime] = React.useState(timeLeft || 0);
const [showExpiredMessage, setShowExpiredMessage] = React.useState(false);
const [initialized, setInitialized] = React.useState(false);
const [connectionBroken, setConnectionBroken] = React.useState(false);
const [connectionBroken, setConnectionBroken] = React.useState(false);
const [loggedHidden, setLoggedHidden] = React.useState(false);
React.useEffect(() => {
if (connectionBroken) {
console.log('⏱️ SessionTimer initialization skipped - connection broken');
if (!loggedHidden) {
console.log('⏱️ SessionTimer initialization skipped - connection broken');
setLoggedHidden(true);
}
return;
}
@@ -23,17 +28,22 @@ const SessionTimer = ({ timeLeft, sessionType, sessionManager }) => {
setCurrentTime(initialTime);
setInitialized(true);
}, [sessionManager, connectionBroken]);
setLoggedHidden(false);
}, [sessionManager, connectionBroken]);
React.useEffect(() => {
if (connectionBroken) {
console.log('⏱️ SessionTimer props update skipped - connection broken');
if (!loggedHidden) {
console.log('⏱️ SessionTimer props update skipped - connection broken');
setLoggedHidden(true);
}
return;
}
if (timeLeft && timeLeft > 0) {
setCurrentTime(timeLeft);
}
setLoggedHidden(false);
}, [timeLeft, connectionBroken]);
React.useEffect(() => {
@@ -42,7 +52,10 @@ const SessionTimer = ({ timeLeft, sessionType, sessionManager }) => {
}
if (connectionBroken) {
console.log('⏱️ Timer interval skipped - connection broken');
if (!loggedHidden) {
console.log('⏱️ Timer interval skipped - connection broken');
setLoggedHidden(true);
}
return;
}
@@ -50,7 +63,6 @@ const SessionTimer = ({ timeLeft, sessionType, sessionManager }) => {
return;
}
const interval = setInterval(() => {
if (connectionBroken) {
console.log('⏱️ Timer interval stopped - connection broken');
@@ -81,22 +93,18 @@ const SessionTimer = ({ timeLeft, sessionType, sessionManager }) => {
}, 1000);
return () => {
clearInterval(interval);
};
}, [initialized, currentTime, sessionManager, connectionBroken]);
}, [initialized, currentTime, sessionManager, connectionBroken]);
React.useEffect(() => {
const handleSessionTimerUpdate = (event) => {
if (event.detail.timeLeft && event.detail.timeLeft > 0) {
setCurrentTime(event.detail.timeLeft);
}
};
const handleForceHeaderUpdate = (event) => {
if (sessionManager && sessionManager.hasActiveSession()) {
const newTime = sessionManager.getTimeLeft();
setCurrentTime(newTime);
@@ -105,28 +113,41 @@ const SessionTimer = ({ timeLeft, sessionType, sessionManager }) => {
const handlePeerDisconnect = (event) => {
console.log('🔌 Peer disconnect detected in SessionTimer - stopping timer permanently');
setConnectionBroken(true);
setConnectionBroken(true);
setCurrentTime(0);
setShowExpiredMessage(false);
setLoggedHidden(false);
};
const handleNewConnection = (event) => {
console.log('🔌 New connection detected in SessionTimer - resetting connection state');
setConnectionBroken(false);
setConnectionBroken(false);
setLoggedHidden(false);
};
const handleConnectionCleaned = (event) => {
console.log('🧹 Connection cleaned - resetting SessionTimer state');
setConnectionBroken(false);
setCurrentTime(0);
setShowExpiredMessage(false);
setInitialized(false);
setLoggedHidden(false);
};
document.addEventListener('session-timer-update', handleSessionTimerUpdate);
document.addEventListener('force-header-update', handleForceHeaderUpdate);
document.addEventListener('peer-disconnect', handlePeerDisconnect);
document.addEventListener('new-connection', handleNewConnection);
document.addEventListener('connection-cleaned', handleConnectionCleaned);
return () => {
document.removeEventListener('session-timer-update', handleSessionTimerUpdate);
document.removeEventListener('force-header-update', handleForceHeaderUpdate);
document.removeEventListener('peer-disconnect', handlePeerDisconnect);
document.removeEventListener('new-connection', handleNewConnection);
document.removeEventListener('connection-cleaned', handleConnectionCleaned);
};
}, [sessionManager]);
}, [sessionManager]);
if (showExpiredMessage) {
return React.createElement('div', {
@@ -145,20 +166,33 @@ const SessionTimer = ({ timeLeft, sessionType, sessionManager }) => {
}
if (!sessionManager) {
console.log('⏱️ SessionTimer hidden - no sessionManager');
if (!loggedHidden) {
console.log('⏱️ SessionTimer hidden - no sessionManager');
setLoggedHidden(true);
}
return null;
}
if (connectionBroken) {
console.log('⏱️ SessionTimer hidden - connection broken');
if (!loggedHidden) {
console.log('⏱️ SessionTimer hidden - connection broken');
setLoggedHidden(true);
}
return null;
}
if (!currentTime || currentTime <= 0) {
console.log('⏱️ SessionTimer hidden - no time left');
if (!loggedHidden) {
console.log('⏱️ SessionTimer hidden - no time left');
setLoggedHidden(true);
}
return null;
}
if (loggedHidden) {
setLoggedHidden(false);
}
const totalMinutes = Math.floor(currentTime / (60 * 1000));
const totalSeconds = Math.floor(currentTime / 1000);
@@ -179,8 +213,8 @@ const SessionTimer = ({ timeLeft, sessionType, sessionManager }) => {
};
const getTimerStyle = () => {
const totalDuration = sessionType === 'demo' ? 6 * 60 * 1000 : 60 * 60 * 1000;
const timeProgress = (totalDuration - currentTime) / totalDuration;
const totalDuration = sessionType === 'demo' ? 6 * 60 * 1000 : 60 * 60 * 1000;
const timeProgress = (totalDuration - currentTime) / totalDuration;
let backgroundColor, textColor, iconColor, iconClass, shouldPulse;
@@ -247,4 +281,4 @@ window.updateSessionTimer = (newTimeLeft, newSessionType) => {
}));
};
console.log('✅ SessionTimer loaded with fixes and improvements');
console.log('✅ SessionTimer loaded with anti-spam logging fixes');

View File

@@ -2505,6 +2505,13 @@ async autoEnableSecurityFeatures() {
securityLevel: offerPackage.securityLevel.level
});
document.dispatchEvent(new CustomEvent('new-connection', {
detail: {
type: 'offer',
timestamp: Date.now()
}
}));
return offerPackage;
} catch (error) {
window.EnhancedSecureCryptoUtils.secureLog.log('error', 'Enhanced secure offer creation failed', {
@@ -2712,6 +2719,13 @@ async autoEnableSecurityFeatures() {
securityLevel: answerPackage.securityLevel.level
});
document.dispatchEvent(new CustomEvent('new-connection', {
detail: {
type: 'answer',
timestamp: Date.now()
}
}));
return answerPackage;
} catch (error) {
window.EnhancedSecureCryptoUtils.secureLog.log('error', 'Enhanced secure answer creation failed', {
@@ -3252,6 +3266,13 @@ async autoEnableSecurityFeatures() {
setTimeout(() => {
this.sendDisconnectNotification();
}, 100);
document.dispatchEvent(new CustomEvent('peer-disconnect', {
detail: {
reason: 'user_disconnect',
timestamp: Date.now()
}
}));
setTimeout(() => {
this.cleanupConnection();
@@ -3263,6 +3284,13 @@ async autoEnableSecurityFeatures() {
this.isVerified = false;
this.onMessage('🔌 Connection lost. Attempting to reconnect...', 'system');
document.dispatchEvent(new CustomEvent('peer-disconnect', {
detail: {
reason: 'connection_lost',
timestamp: Date.now()
}
}));
setTimeout(() => {
if (!this.intentionalDisconnect) {
this.attemptReconnection();
@@ -3322,6 +3350,13 @@ async autoEnableSecurityFeatures() {
this.onKeyExchange('');
this.onVerificationRequired('');
document.dispatchEvent(new CustomEvent('peer-disconnect', {
detail: {
reason: reason,
timestamp: Date.now()
}
}));
setTimeout(() => {
this.cleanupConnection();
}, 2000);
@@ -3390,6 +3425,13 @@ async autoEnableSecurityFeatures() {
// IMPORTANT: Clearing security logs
window.EnhancedSecureCryptoUtils.secureLog.clearLogs();
document.dispatchEvent(new CustomEvent('connection-cleaned', {
detail: {
timestamp: Date.now(),
reason: this.intentionalDisconnect ? 'user_cleanup' : 'automatic_cleanup'
}
}));
// Notifying the UI about complete cleanup
this.onStatusChange('disconnected');
this.onKeyExchange('');

View File

@@ -55,9 +55,24 @@ class PayPerSessionManager {
this.startDemoSessionCleanup();
this.startActiveDemoSessionCleanup();
this.globalDemoCounter = 0;
this.memoryStorage = new Map();
this.currentTabId = null;
this.tabHeartbeatInterval = null;
this.initializePersistentStorage();
this.performEnhancedCleanup();
const multiTabCheck = this.checkMultiTabProtection();
if (!multiTabCheck.allowed) {
console.warn('❌ Multi-tab protection triggered:', multiTabCheck.message);
}
console.log('💰 PayPerSessionManager initialized with ENHANCED secure demo mode');
setInterval(() => {
this.savePersistentData();
}, 30000);
console.log('💰 PayPerSessionManager initialized with ENHANCED secure demo mode and auto-save');
}
// ============================================
@@ -128,9 +143,10 @@ class PayPerSessionManager {
}
// IMPROVED user fingerprint generation
generateUserFingerprint() {
generateAdvancedUserFingerprint() {
try {
const components = [
// Базовые компоненты (как было)
const basicComponents = [
navigator.userAgent || '',
navigator.language || '',
screen.width + 'x' + screen.height,
@@ -144,136 +160,382 @@ class PayPerSessionManager {
navigator.maxTouchPoints || 0,
navigator.onLine ? '1' : '0'
];
// НОВЫЕ КОМПОНЕНТЫ ДЛЯ HARDWARE BINDING
const hardwareComponents = [];
// Create a more secure hash
let hash = 0;
const str = components.join('|');
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
// WebGL отпечаток (очень сложно подделать)
try {
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
if (gl) {
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
if (debugInfo) {
hardwareComponents.push(gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL) || '');
hardwareComponents.push(gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) || '');
}
hardwareComponents.push(gl.getParameter(gl.VERSION) || '');
hardwareComponents.push(gl.getParameter(gl.SHADING_LANGUAGE_VERSION) || '');
}
} catch (e) {
hardwareComponents.push('webgl_error');
}
// Canvas отпечаток (уникален для каждого устройства)
try {
const canvas = document.createElement('canvas');
canvas.width = 200;
canvas.height = 50;
const ctx = canvas.getContext('2d');
ctx.textBaseline = 'top';
ctx.font = '14px Arial';
ctx.fillText('SecureBit Demo Fingerprint 🔒', 2, 2);
ctx.fillStyle = 'rgba(255,0,0,0.5)';
ctx.fillRect(50, 10, 20, 20);
hardwareComponents.push(canvas.toDataURL());
} catch (e) {
hardwareComponents.push('canvas_error');
}
// Аудио отпечаток (очень стабилен)
try {
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
const oscillator = audioContext.createOscillator();
const analyser = audioContext.createAnalyser();
const gain = audioContext.createGain();
oscillator.connect(analyser);
analyser.connect(gain);
gain.connect(audioContext.destination);
oscillator.frequency.setValueAtTime(1000, audioContext.currentTime);
gain.gain.setValueAtTime(0, audioContext.currentTime);
hardwareComponents.push(audioContext.sampleRate.toString());
hardwareComponents.push(audioContext.state);
hardwareComponents.push(analyser.frequencyBinCount.toString());
audioContext.close();
} catch (e) {
hardwareComponents.push('audio_error');
}
// Производительность CPU (стабильна для устройства)
const cpuBenchmark = this.performCPUBenchmark();
hardwareComponents.push(cpuBenchmark);
// Объединяем все компоненты
const allComponents = [...basicComponents, ...hardwareComponents];
// Создаем несколько уровней хеширования
let primaryHash = 0;
let secondaryHash = 0;
let tertiaryHash = 0;
const primaryStr = allComponents.slice(0, 8).join('|');
const secondaryStr = allComponents.slice(8, 16).join('|');
const tertiaryStr = allComponents.slice(16).join('|');
// Первичный хеш
for (let i = 0; i < primaryStr.length; i++) {
const char = primaryStr.charCodeAt(i);
primaryHash = ((primaryHash << 7) - primaryHash) + char;
primaryHash = primaryHash & primaryHash;
}
// Add extra salt for stability
const salt = 'securebit_demo_2024';
const saltedStr = str + salt;
let saltedHash = 0;
for (let i = 0; i < saltedStr.length; i++) {
const char = saltedStr.charCodeAt(i);
saltedHash = ((saltedHash << 5) - saltedHash) + char;
saltedHash = saltedHash & saltedHash;
// Вторичный хеш
for (let i = 0; i < secondaryStr.length; i++) {
const char = secondaryStr.charCodeAt(i);
secondaryHash = ((secondaryHash << 11) - secondaryHash) + char;
secondaryHash = secondaryHash & secondaryHash;
}
return Math.abs(hash).toString(36) + '_' + Math.abs(saltedHash).toString(36);
// Третичный хеш
for (let i = 0; i < tertiaryStr.length; i++) {
const char = tertiaryStr.charCodeAt(i);
tertiaryHash = ((tertiaryHash << 13) - tertiaryHash) + char;
tertiaryHash = tertiaryHash & tertiaryHash;
}
// Комбинированный отпечаток
const combined = `${Math.abs(primaryHash).toString(36)}_${Math.abs(secondaryHash).toString(36)}_${Math.abs(tertiaryHash).toString(36)}`;
console.log('🔒 Enhanced fingerprint generated:', {
components: allComponents.length,
primaryLength: primaryStr.length,
secondaryLength: secondaryStr.length,
tertiaryLength: tertiaryStr.length,
fingerprintLength: combined.length
});
return combined;
} catch (error) {
console.warn('Failed to generate user fingerprint:', error);
return 'fallback_' + Math.random().toString(36).substr(2, 9);
console.warn('Failed to generate enhanced fingerprint:', error);
// Fallback к более простому отпечатку
return 'fallback_' + Date.now().toString(36) + '_' + Math.random().toString(36).substr(2, 9);
}
}
// COMPLETELY REWRITTEN demo session limits check
checkDemoSessionLimits(userFingerprint) {
const userData = this.demoSessions.get(userFingerprint);
const now = Date.now();
performCPUBenchmark() {
const start = performance.now();
let result = 0;
console.log(`🔍 Checking demo limits for user ${userFingerprint.substring(0, 12)}...`);
for (let i = 0; i < 100000; i++) {
result += Math.sin(i) * Math.cos(i);
}
// CHECK 1: Global limit of simultaneous demo sessions
if (this.activeDemoSessions.size >= this.maxGlobalDemoSessions) {
console.log(`❌ Global demo limit reached: ${this.activeDemoSessions.size}/${this.maxGlobalDemoSessions}`);
const end = performance.now();
const duration = Math.round(end - start);
if (duration < 5) return 'fast_cpu';
if (duration < 15) return 'medium_cpu';
if (duration < 30) return 'slow_cpu';
return 'very_slow_cpu';
}
initializePersistentStorage() {
this.storageKeys = {
demoSessions: 'sb_demo_sessions_v2',
completedSessions: 'sb_completed_sessions_v2',
globalCounter: 'sb_global_demo_counter_v2',
lastCleanup: 'sb_last_cleanup_v2',
hardwareFingerprint: 'sb_hw_fingerprint_v2'
};
this.loadPersistentData();
}
loadPersistentData() {
try {
const savedDemoSessions = this.getFromStorage(this.storageKeys.demoSessions);
if (savedDemoSessions) {
const parsed = JSON.parse(savedDemoSessions);
for (const [key, value] of Object.entries(parsed)) {
this.demoSessions.set(key, value);
}
}
const savedCompletedSessions = this.getFromStorage(this.storageKeys.completedSessions);
if (savedCompletedSessions) {
const parsed = JSON.parse(savedCompletedSessions);
for (const [key, value] of Object.entries(parsed)) {
this.completedDemoSessions.set(key, value);
}
}
const savedGlobalCounter = this.getFromStorage(this.storageKeys.globalCounter);
if (savedGlobalCounter) {
this.globalDemoCounter = parseInt(savedGlobalCounter) || 0;
} else {
this.globalDemoCounter = 0;
}
console.log('📊 Persistent data loaded:', {
demoSessions: this.demoSessions.size,
completedSessions: this.completedDemoSessions.size,
globalCounter: this.globalDemoCounter
});
} catch (error) {
console.warn('Failed to load persistent data:', error);
this.globalDemoCounter = 0;
}
}
savePersistentData() {
try {
const demoSessionsObj = Object.fromEntries(this.demoSessions);
this.setToStorage(this.storageKeys.demoSessions, JSON.stringify(demoSessionsObj));
const completedSessionsObj = Object.fromEntries(this.completedDemoSessions);
this.setToStorage(this.storageKeys.completedSessions, JSON.stringify(completedSessionsObj));
this.setToStorage(this.storageKeys.globalCounter, this.globalDemoCounter.toString());
this.setToStorage(this.storageKeys.lastCleanup, Date.now().toString());
} catch (error) {
console.warn('Failed to save persistent data:', error);
}
}
getFromStorage(key) {
try {
if (typeof localStorage !== 'undefined') {
const value = localStorage.getItem(key);
if (value) return value;
}
} catch (e) {}
try {
if (typeof sessionStorage !== 'undefined') {
const value = sessionStorage.getItem(key);
if (value) return value;
}
} catch (e) {}
try {
if ('caches' in window) {
}
} catch (e) {}
return null;
}
setToStorage(key, value) {
try {
if (typeof localStorage !== 'undefined') {
localStorage.setItem(key, value);
}
} catch (e) {}
try {
if (typeof sessionStorage !== 'undefined') {
sessionStorage.setItem(key, value);
}
} catch (e) {}
if (!this.memoryStorage) this.memoryStorage = new Map();
this.memoryStorage.set(key, value);
}
checkAntiResetProtection(userFingerprint) {
if (!this.globalDemoCounter) {
this.globalDemoCounter = 0;
}
const hardwareFingerprint = this.getHardwareFingerprint();
const savedHardwareFingerprint = this.getFromStorage(this.storageKeys.hardwareFingerprint);
if (savedHardwareFingerprint && savedHardwareFingerprint !== hardwareFingerprint) {
console.warn('🚨 Hardware fingerprint mismatch detected - possible reset attempt');
this.globalDemoCounter += 5;
this.savePersistentData();
return {
allowed: false,
isValid: false,
reason: 'hardware_mismatch',
penalty: 5
};
}
this.setToStorage(this.storageKeys.hardwareFingerprint, hardwareFingerprint);
if (this.globalDemoCounter >= 10) {
return {
isValid: false,
reason: 'global_limit_exceeded',
message: `Too many demo sessions active globally (${this.activeDemoSessions.size}/${this.maxGlobalDemoSessions}). Please try again later.`,
remaining: 0,
debugInfo: `Global sessions: ${this.activeDemoSessions.size}/${this.maxGlobalDemoSessions}`
globalCount: this.globalDemoCounter
};
}
if (!userData) {
// First demo session for this user
console.log(`✅ First demo session for user ${userFingerprint.substring(0, 12)}`);
return {
allowed: true,
reason: 'first_demo_session',
remaining: this.maxDemoSessionsPerUser,
debugInfo: 'First time user'
};
return {
isValid: true,
globalCount: this.globalDemoCounter
};
}
getHardwareFingerprint() {
const components = [];
// CPU информация
components.push(navigator.hardwareConcurrency || 0);
components.push(navigator.deviceMemory || 0);
try {
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl');
if (gl) {
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
if (debugInfo) {
components.push(gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL) || '');
components.push(gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) || '');
}
}
} catch (e) {
components.push('webgl_unavailable');
}
// CHECK 2: Limit sessions per 24 hours (STRICT check)
const sessionsLast24h = userData.sessions.filter(session =>
now - session.timestamp < this.demoCooldownPeriod
);
components.push(screen.width);
components.push(screen.height);
components.push(screen.colorDepth);
components.push(Intl.DateTimeFormat().resolvedOptions().timeZone);
console.log(`📊 Sessions in last 24h for user ${userFingerprint.substring(0, 12)}: ${sessionsLast24h.length}/${this.maxDemoSessionsPerUser}`);
if (sessionsLast24h.length >= this.maxDemoSessionsPerUser) {
const oldestSession = Math.min(...sessionsLast24h.map(s => s.timestamp));
const timeUntilNext = this.demoCooldownPeriod - (now - oldestSession);
console.log(`❌ Daily demo limit exceeded for user ${userFingerprint.substring(0, 12)}`);
return {
allowed: false,
reason: 'daily_limit_exceeded',
timeUntilNext: timeUntilNext,
message: `Daily demo limit reached (${this.maxDemoSessionsPerUser}/day). Next session available in ${Math.ceil(timeUntilNext / (60 * 1000))} minutes.`,
remaining: 0,
debugInfo: `Used ${sessionsLast24h.length}/${this.maxDemoSessionsPerUser} today`
};
let hash = 0;
const str = components.join('|');
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
// CHECK 3: Cooldown between sessions (FIXED LOGIC)
if (userData.lastUsed && (now - userData.lastUsed) < this.demoSessionCooldown) {
const timeUntilNext = this.demoSessionCooldown - (now - userData.lastUsed);
const minutesLeft = Math.ceil(timeUntilNext / (60 * 1000));
console.log(`⏰ Cooldown active for user ${userFingerprint.substring(0, 12)}: ${minutesLeft} minutes`);
return {
allowed: false,
reason: 'session_cooldown',
timeUntilNext: timeUntilNext,
message: `Please wait ${minutesLeft} minutes between demo sessions. This prevents abuse and ensures fair access for all users.`,
remaining: this.maxDemoSessionsPerUser - sessionsLast24h.length,
debugInfo: `Cooldown: ${minutesLeft}min left, last used: ${Math.round((now - userData.lastUsed) / (60 * 1000))}min ago`
};
}
return Math.abs(hash).toString(36);
}
registerEnhancedDemoSessionUsage(userFingerprint, preimage) {
// Вызываем оригинальный метод
const session = this.registerDemoSessionUsage(userFingerprint, preimage);
// CHECK 4: NEW - Check for completed sessions
const completedSessions = this.completedDemoSessions.get(userFingerprint) || [];
const recentCompletedSessions = completedSessions.filter(session =>
now - session.endTime < this.minTimeBetweenCompletedSessions
);
// Дополнительно сохраняем в persistent storage
this.savePersistentData();
console.log('📊 Enhanced demo session registered:', {
userFingerprint: userFingerprint.substring(0, 12),
globalCount: this.globalDemoCounter,
sessionId: session.sessionId,
timestamp: new Date().toISOString()
});
if (recentCompletedSessions.length > 0) {
const lastCompletedSession = Math.max(...recentCompletedSessions.map(s => s.endTime));
const timeUntilNext = this.minTimeBetweenCompletedSessions - (now - lastCompletedSession);
console.log(`⏰ Recent session completed, waiting period active for user ${userFingerprint.substring(0, 12)}`);
return session;
}
// COMPLETELY REWRITTEN demo session limits check
checkEnhancedDemoSessionLimits(userFingerprint) {
const antiResetCheck = this.checkAntiResetProtection(userFingerprint);
if (!antiResetCheck.isValid) {
return {
allowed: false,
reason: 'recent_session_completed',
timeUntilNext: timeUntilNext,
message: `Please wait ${Math.ceil(timeUntilNext / (60 * 1000))} minutes after your last session before starting a new one.`,
remaining: this.maxDemoSessionsPerUser - sessionsLast24h.length,
debugInfo: `Last session ended ${Math.round((now - lastCompletedSession) / (60 * 1000))}min ago`
reason: antiResetCheck.reason,
message: this.getAntiResetMessage(antiResetCheck),
globalCount: antiResetCheck.globalCount,
penalty: antiResetCheck.penalty
};
}
console.log(`Demo session approved for user ${userFingerprint.substring(0, 12)}`);
return {
allowed: true,
reason: 'within_limits',
remaining: this.maxDemoSessionsPerUser - sessionsLast24h.length,
debugInfo: `Available: ${this.maxDemoSessionsPerUser - sessionsLast24h.length}/${this.maxDemoSessionsPerUser}`
const regularCheck = this.checkDemoSessionLimits(userFingerprint);
if (regularCheck.allowed) {
this.globalDemoCounter++;
this.savePersistentData();
}
return {
...regularCheck,
globalCount: this.globalDemoCounter
};
}
getAntiResetMessage(antiResetCheck) {
switch (antiResetCheck.reason) {
case 'hardware_mismatch':
return 'Обнаружена попытка сброса ограничений. Доступ к демо-режиму временно ограничен.';
case 'global_limit_exceeded':
return `Глобальный лимит демо-сессий превышен (${antiResetCheck.globalCount}/10). Для продолжения требуется оплаченная сессия.`;
default:
return 'Доступ к демо-режиму ограничен по соображениям безопасности.';
}
}
// FIXED demo session usage registration
registerDemoSessionUsage(userFingerprint, preimage) {
registerDemoSessionUsage(userFingerprint, preimage) {
const now = Date.now();
const userData = this.demoSessions.get(userFingerprint) || {
count: 0,
@@ -312,6 +574,131 @@ class PayPerSessionManager {
return newSession;
}
performEnhancedCleanup() {
const now = Date.now();
const lastCleanup = parseInt(this.getFromStorage(this.storageKeys.lastCleanup)) || 0;
if (now - lastCleanup < 6 * 60 * 60 * 1000) {
return;
}
console.log('🧹 Performing enhanced cleanup...');
const maxAge = 25 * 60 * 60 * 1000;
let cleanedSessions = 0;
for (const [identifier, data] of this.demoSessions.entries()) {
if (now - data.lastUsed > maxAge) {
this.demoSessions.delete(identifier);
cleanedSessions++;
}
}
let cleanedCompleted = 0;
for (const [identifier, sessions] of this.completedDemoSessions.entries()) {
const filteredSessions = sessions.filter(session =>
now - session.endTime < maxAge
);
if (filteredSessions.length === 0) {
this.completedDemoSessions.delete(identifier);
cleanedCompleted++;
} else {
this.completedDemoSessions.set(identifier, filteredSessions);
}
}
const weekAgo = 7 * 24 * 60 * 60 * 1000;
if (now - lastCleanup > weekAgo) {
this.globalDemoCounter = Math.max(0, this.globalDemoCounter - 3);
console.log('🔄 Global demo counter reset (weekly):', this.globalDemoCounter);
}
this.savePersistentData();
console.log('✅ Enhanced cleanup completed:', {
cleanedSessions,
cleanedCompleted,
globalCounter: this.globalDemoCounter
});
}
checkMultiTabProtection() {
const tabId = 'tab_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
const activeTabsKey = 'sb_active_tabs';
try {
const activeTabsStr = this.getFromStorage(activeTabsKey);
const activeTabs = activeTabsStr ? JSON.parse(activeTabsStr) : [];
const now = Date.now();
const validTabs = activeTabs.filter(tab => now - tab.timestamp < 30000);
if (validTabs.length >= 2) {
return {
allowed: false,
reason: 'multiple_tabs',
message: 'Демо-режим доступен только в одной вкладке одновременно.'
};
}
validTabs.push({
tabId: tabId,
timestamp: now
});
this.setToStorage(activeTabsKey, JSON.stringify(validTabs));
this.currentTabId = tabId;
this.startTabHeartbeat();
return {
allowed: true,
tabId: tabId
};
} catch (error) {
console.warn('Multi-tab protection error:', error);
return { allowed: true };
}
}
startTabHeartbeat() {
if (this.tabHeartbeatInterval) {
clearInterval(this.tabHeartbeatInterval);
}
this.tabHeartbeatInterval = setInterval(() => {
this.updateTabHeartbeat();
}, 10000);
}
updateTabHeartbeat() {
if (!this.currentTabId) return;
try {
const activeTabsKey = 'sb_active_tabs';
const activeTabsStr = this.getFromStorage(activeTabsKey);
const activeTabs = activeTabsStr ? JSON.parse(activeTabsStr) : [];
// Обновляем timestamp для текущей вкладки
const updatedTabs = activeTabs.map(tab => {
if (tab.tabId === this.currentTabId) {
return {
...tab,
timestamp: Date.now()
};
}
return tab;
});
this.setToStorage(activeTabsKey, JSON.stringify(updatedTabs));
} catch (error) {
console.warn('Tab heartbeat update failed:', error);
}
}
// NEW method: Register demo session completion
registerDemoSessionCompletion(userFingerprint, sessionDuration, preimage) {
const now = Date.now();
@@ -504,9 +891,9 @@ class PayPerSessionManager {
throw new Error('Demo preimage timestamp from future - possible clock manipulation');
}
// CHECK 4: Custom Limits
const userFingerprint = this.generateUserFingerprint();
const limitsCheck = this.checkDemoSessionLimits(userFingerprint);
// CHECK 4: ИСПРАВЛЕННЫЙ вызов лимитов - используем ПРАВИЛЬНЫЙ метод
const userFingerprint = this.generateAdvancedUserFingerprint(); // ИСПРАВЛЕНО!
const limitsCheck = this.checkEnhancedDemoSessionLimits(userFingerprint); // ИСПРАВЛЕНО!
if (!limitsCheck.allowed) {
throw new Error(`Demo session limits exceeded: ${limitsCheck.message}`);
@@ -514,7 +901,7 @@ class PayPerSessionManager {
// FIX: For demo sessions, do NOT add preimage to usedPreimages here,
// as this will only be done after successful activation
this.registerDemoSessionUsage(userFingerprint, preimage);
this.registerEnhancedDemoSessionUsage(userFingerprint, preimage); // ИСПРАВЛЕНО!
console.log('✅ Demo preimage ENHANCED validation passed');
return true;
@@ -552,6 +939,7 @@ class PayPerSessionManager {
}
}
// ============================================
// LIGHTNING NETWORK INTEGRATION
// ============================================
@@ -933,9 +1321,9 @@ class PayPerSessionManager {
};
}
// ADDITIONAL check at activation level
const userFingerprint = this.generateUserFingerprint();
const demoCheck = this.checkDemoSessionLimits(userFingerprint);
// ИСПРАВЛЕННЫЙ вызов - используем правильные методы
const userFingerprint = this.generateAdvancedUserFingerprint();
const demoCheck = this.checkEnhancedDemoSessionLimits(userFingerprint);
if (!demoCheck.allowed) {
console.log(`⚠️ Demo session cooldown active, but allowing activation for development`);
@@ -1115,7 +1503,7 @@ class PayPerSessionManager {
handleDemoSessionExpiry(preimage) {
if (this.currentSession && this.currentSession.preimage === preimage) {
const userFingerprint = this.generateUserFingerprint();
const userFingerprint = this.generateAdvancedUserFingerprint(); // ИСПРАВЛЕНО!
const sessionDuration = Date.now() - this.currentSession.startTime;
this.registerDemoSessionCompletion(userFingerprint, sessionDuration, preimage);
@@ -1145,7 +1533,7 @@ class PayPerSessionManager {
const expiredSession = this.currentSession;
if (expiredSession && expiredSession.isDemo) {
const userFingerprint = this.generateUserFingerprint();
const userFingerprint = this.generateAdvancedUserFingerprint(); // ИСПРАВЛЕНО!
const sessionDuration = Date.now() - expiredSession.startTime;
this.registerDemoSessionCompletion(userFingerprint, sessionDuration, expiredSession.preimage);
}
@@ -1194,8 +1582,8 @@ class PayPerSessionManager {
// UPDATED demo session creation
createDemoSession() {
const userFingerprint = this.generateUserFingerprint();
const demoCheck = this.checkDemoSessionLimits(userFingerprint);
const userFingerprint = this.generateAdvancedUserFingerprint(); // ИСПРАВЛЕНО!
const demoCheck = this.checkEnhancedDemoSessionLimits(userFingerprint); // ИСПРАВЛЕНО!
if (!demoCheck.allowed) {
return {
@@ -1248,7 +1636,7 @@ class PayPerSessionManager {
// UPDATED information about demo limits
getDemoSessionInfo() {
const userFingerprint = this.generateUserFingerprint();
const userFingerprint = this.generateAdvancedUserFingerprint();
const userData = this.demoSessions.get(userFingerprint);
const now = Date.now();
@@ -1519,7 +1907,7 @@ class PayPerSessionManager {
// IMPORTANT: For demo sessions, we register forced termination
if (resetSession && resetSession.isDemo) {
const userFingerprint = this.generateUserFingerprint();
const userFingerprint = this.generateAdvancedUserFingerprint();
const sessionDuration = Date.now() - resetSession.startTime;
this.registerDemoSessionCompletion(userFingerprint, sessionDuration, resetSession.preimage);
}
@@ -1555,7 +1943,7 @@ class PayPerSessionManager {
// IMPORTANT: We register the end of the current demo session during cleanup
if (this.currentSession && this.currentSession.isDemo) {
const userFingerprint = this.generateUserFingerprint();
const userFingerprint = this.generateAdvancedUserFingerprint(); // ИСПРАВЛЕНО!
const sessionDuration = Date.now() - this.currentSession.startTime;
this.registerDemoSessionCompletion(userFingerprint, sessionDuration, this.currentSession.preimage);
}
@@ -1588,7 +1976,7 @@ class PayPerSessionManager {
}
getVerifiedDemoSession() {
const userFingerprint = this.generateUserFingerprint();
const userFingerprint = this.generateAdvancedUserFingerprint();
const userData = this.demoSessions.get(userFingerprint);
console.log('🔍 Searching for verified demo session:', {
@@ -1644,8 +2032,106 @@ class PayPerSessionManager {
return verifiedSession;
}
checkDemoSessionLimits(userFingerprint) {
const userData = this.demoSessions.get(userFingerprint);
const now = Date.now();
console.log(`🔍 Checking demo limits for user ${userFingerprint.substring(0, 12)}...`);
// CHECK 1: Global limit of simultaneous demo sessions
if (this.activeDemoSessions.size >= this.maxGlobalDemoSessions) {
console.log(`❌ Global demo limit reached: ${this.activeDemoSessions.size}/${this.maxGlobalDemoSessions}`);
return {
allowed: false,
reason: 'global_limit_exceeded',
message: `Too many demo sessions active globally (${this.activeDemoSessions.size}/${this.maxGlobalDemoSessions}). Please try again later.`,
remaining: 0,
debugInfo: `Global sessions: ${this.activeDemoSessions.size}/${this.maxGlobalDemoSessions}`
};
}
if (!userData) {
// First demo session for this user
console.log(`✅ First demo session for user ${userFingerprint.substring(0, 12)}`);
return {
allowed: true,
reason: 'first_demo_session',
remaining: this.maxDemoSessionsPerUser,
debugInfo: 'First time user'
};
}
// CHECK 2: Limit sessions per 24 hours (STRICT check)
const sessionsLast24h = userData.sessions.filter(session =>
now - session.timestamp < this.demoCooldownPeriod
);
console.log(`📊 Sessions in last 24h for user ${userFingerprint.substring(0, 12)}: ${sessionsLast24h.length}/${this.maxDemoSessionsPerUser}`);
if (sessionsLast24h.length >= this.maxDemoSessionsPerUser) {
const oldestSession = Math.min(...sessionsLast24h.map(s => s.timestamp));
const timeUntilNext = this.demoCooldownPeriod - (now - oldestSession);
console.log(`❌ Daily demo limit exceeded for user ${userFingerprint.substring(0, 12)}`);
return {
allowed: false,
reason: 'daily_limit_exceeded',
timeUntilNext: timeUntilNext,
message: `Daily demo limit reached (${this.maxDemoSessionsPerUser}/day). Next session available in ${Math.ceil(timeUntilNext / (60 * 1000))} minutes.`,
remaining: 0,
debugInfo: `Used ${sessionsLast24h.length}/${this.maxDemoSessionsPerUser} today`
};
}
// CHECK 3: Cooldown between sessions (FIXED LOGIC)
if (userData.lastUsed && (now - userData.lastUsed) < this.demoSessionCooldown) {
const timeUntilNext = this.demoSessionCooldown - (now - userData.lastUsed);
const minutesLeft = Math.ceil(timeUntilNext / (60 * 1000));
console.log(`⏰ Cooldown active for user ${userFingerprint.substring(0, 12)}: ${minutesLeft} minutes`);
return {
allowed: false,
reason: 'session_cooldown',
timeUntilNext: timeUntilNext,
message: `Please wait ${minutesLeft} minutes between demo sessions. This prevents abuse and ensures fair access for all users.`,
remaining: this.maxDemoSessionsPerUser - sessionsLast24h.length,
debugInfo: `Cooldown: ${minutesLeft}min left, last used: ${Math.round((now - userData.lastUsed) / (60 * 1000))}min ago`
};
}
// CHECK 4: NEW - Check for completed sessions
const completedSessions = this.completedDemoSessions.get(userFingerprint) || [];
const recentCompletedSessions = completedSessions.filter(session =>
now - session.endTime < this.minTimeBetweenCompletedSessions
);
if (recentCompletedSessions.length > 0) {
const lastCompletedSession = Math.max(...recentCompletedSessions.map(s => s.endTime));
const timeUntilNext = this.minTimeBetweenCompletedSessions - (now - lastCompletedSession);
console.log(`⏰ Recent session completed, waiting period active for user ${userFingerprint.substring(0, 12)}`);
return {
allowed: false,
reason: 'recent_session_completed',
timeUntilNext: timeUntilNext,
message: `Please wait ${Math.ceil(timeUntilNext / (60 * 1000))} minutes after your last session before starting a new one.`,
remaining: this.maxDemoSessionsPerUser - sessionsLast24h.length,
debugInfo: `Last session ended ${Math.round((now - lastCompletedSession) / (60 * 1000))}min ago`
};
}
console.log(`✅ Demo session approved for user ${userFingerprint.substring(0, 12)}`);
return {
allowed: true,
reason: 'within_limits',
remaining: this.maxDemoSessionsPerUser - sessionsLast24h.length,
debugInfo: `Available: ${this.maxDemoSessionsPerUser - sessionsLast24h.length}/${this.maxDemoSessionsPerUser}`
};
}
createDemoSessionForActivation() {
const userFingerprint = this.generateUserFingerprint();
const userFingerprint = this.generateAdvancedUserFingerprint(); // ИСПРАВЛЕНО!
if (this.activeDemoSessions.size >= this.maxGlobalDemoSessions) {
return {