diff --git a/src/main.js b/src/main.js index 3011b50..486b290 100644 --- a/src/main.js +++ b/src/main.js @@ -18,23 +18,28 @@ app.use(router); // Получаем экземпляр нашего auth "стора" const { fetchUser, handleDeviceBlocked } = useAuth(); +// Проверка на наличие браузерного окружения +const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined'; + // Асинхронная самовызывающаяся функция для инициализации (async () => { try { - // Инициализируем систему безопасности устройств - console.log('Инициализация системы безопасности устройств...'); - await deviceSecurityService.initialize(); - - // Подписываемся на события блокировки устройства - deviceSecurityService.onDeviceBlocked(handleDeviceBlocked); - - console.log('Система безопасности устройств инициализирована'); - - // Проверяем, не заблокировано ли устройство - if (deviceSecurityService.isDeviceBlocked()) { - console.warn('Устройство заблокировано, перенаправляем на страницу входа'); - window.location.href = '/login?blocked=true&type=device'; - return; + // Инициализируем систему безопасности устройств только в браузере + if (isBrowser) { + console.log('Инициализация системы безопасности устройств...'); + await deviceSecurityService.initialize(); + + // Подписываемся на события блокировки устройства + deviceSecurityService.onDeviceBlocked(handleDeviceBlocked); + + console.log('Система безопасности устройств инициализирована'); + + // Проверяем, не заблокировано ли устройство + if (deviceSecurityService.isDeviceBlocked()) { + console.warn('Устройство заблокировано, перенаправляем на страницу входа'); + window.location.href = '/login?blocked=true&type=device'; + return; + } } await fetchUser(); // Пытаемся загрузить пользователя по токену из localStorage diff --git a/src/services/deviceSecurity.js b/src/services/deviceSecurity.js index b5e82f7..34628ba 100644 --- a/src/services/deviceSecurity.js +++ b/src/services/deviceSecurity.js @@ -1,32 +1,48 @@ import DeviceFingerprint from '../utils/deviceFingerprint.js'; -import { api } from './api.js'; +import api from './api.js'; + +// Проверка на наличие браузерного окружения +const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined'; /** * Сервис для работы с системой блокировки устройств */ class DeviceSecurityService { constructor() { - this.deviceFingerprint = new DeviceFingerprint(); - this.currentFingerprint = null; - this.isBlocked = false; - this.initialized = false; - this.suspiciousActivities = []; - this.lastFingerprintCheck = null; - this.checkInterval = null; - - // Настройки мониторинга - this.config = { - checkIntervalMs: 30000, // Проверка каждые 30 секунд - maxSuspiciousActivities: 10, - reportThreshold: 5, // Отправлять отчет после 5 подозрительных активностей - fingerprintChangeThreshold: 0.1 // Порог изменения отпечатка (10%) - }; + // Инициализируем только в браузерном окружении + if (isBrowser) { + this.deviceFingerprint = new DeviceFingerprint(); + this.currentFingerprint = null; + this.isBlocked = false; + this.initialized = false; + this.suspiciousActivities = []; + this.lastFingerprintCheck = null; + this.checkInterval = null; + + // Настройки мониторинга + this.config = { + checkIntervalMs: 30000, // Проверка каждые 30 секунд + maxSuspiciousActivities: 10, + reportThreshold: 5, // Отправлять отчет после 5 подозрительных активностей + fingerprintChangeThreshold: 0.1 // Порог изменения отпечатка (10%) + }; + } } /** * Инициализация сервиса безопасности */ async initialize() { + // Проверяем, находимся ли мы в браузерном окружении + if (!isBrowser) { + console.log('[DeviceSecurity] Инициализация пропущена - не браузерное окружение'); + return { + fingerprint: null, + isBlocked: false, + initialized: false + }; + } + try { console.log('[DeviceSecurity] Инициализация сервиса безопасности устройств...'); @@ -58,6 +74,8 @@ class DeviceSecurityService { * Проверка статуса блокировки устройства */ async checkDeviceBlockStatus() { + if (!isBrowser) return true; + try { const response = await api.post('/security/check-device', { fingerprint: this.currentFingerprint, @@ -94,6 +112,8 @@ class DeviceSecurityService { * Получение информации об устройстве */ getDeviceInfo() { + if (!isBrowser) return {}; + return { userAgent: navigator.userAgent, platform: navigator.platform, @@ -109,6 +129,8 @@ class DeviceSecurityService { * Получение количества посещений (из localStorage) */ getVisitCount() { + if (!isBrowser) return 1; + try { const visits = localStorage.getItem('device_visits'); const count = visits ? parseInt(visits) + 1 : 1; @@ -123,6 +145,8 @@ class DeviceSecurityService { * Запуск периодического мониторинга */ startMonitoring() { + if (!isBrowser) return; + if (this.checkInterval) { clearInterval(this.checkInterval); } @@ -142,6 +166,8 @@ class DeviceSecurityService { * Остановка мониторинга */ stopMonitoring() { + if (!isBrowser) return; + if (this.checkInterval) { clearInterval(this.checkInterval); this.checkInterval = null; @@ -153,6 +179,8 @@ class DeviceSecurityService { * Выполнение периодической проверки безопасности */ async performSecurityCheck() { + if (!isBrowser) return; + try { // Проверяем, не изменился ли отпечаток устройства await this.checkFingerprintChanges(); @@ -382,45 +410,9 @@ class DeviceSecurityService { * Проверка блокировки */ isDeviceBlocked() { + if (!isBrowser) return false; return this.isBlocked; } - - /** - * Получение компонентов отпечатка (для отладки) - */ - getFingerprintComponents() { - return this.deviceFingerprint.getComponents(); - } - - /** - * Получение статистики подозрительных активностей - */ - getSuspiciousActivitiesStats() { - return { - total: this.suspiciousActivities.length, - activities: this.suspiciousActivities.slice(), - lastCheck: this.lastFingerprintCheck - }; - } - - /** - * Очистка истории (для отладки) - */ - clearHistory() { - this.suspiciousActivities = []; - localStorage.removeItem('login_attempts'); - console.log('[DeviceSecurity] История очищена'); - } - - /** - * Деструктор - */ - destroy() { - this.stopMonitoring(); - this.suspiciousActivities = []; - this.initialized = false; - console.log('[DeviceSecurity] Сервис уничтожен'); - } } // Создаем единственный экземпляр сервиса diff --git a/src/utils/deviceFingerprint.js b/src/utils/deviceFingerprint.js index 74892d9..2b694fc 100644 --- a/src/utils/deviceFingerprint.js +++ b/src/utils/deviceFingerprint.js @@ -3,6 +3,9 @@ * Используется для блокировки на уровне железа */ +// Проверка на наличие браузерного окружения +const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined'; + class DeviceFingerprint { constructor() { this.components = new Map(); @@ -14,6 +17,14 @@ class DeviceFingerprint { * Инициализация и создание отпечатка устройства */ async initialize() { + // Если мы не в браузере - возвращаем фиктивный отпечаток + if (!isBrowser) { + console.log('[DeviceFingerprint] Не браузерное окружение, отпечаток не создаётся'); + this.fingerprint = 'server-environment-no-fingerprint'; + this.initialized = true; + return this.fingerprint; + } + try { console.log('[DeviceFingerprint] Начинаем инициализацию отпечатка устройства...'); @@ -53,6 +64,8 @@ class DeviceFingerprint { * Информация об экране */ async collectScreenInfo() { + if (!isBrowser) return; + try { this.components.set('screen', { width: screen.width, @@ -72,6 +85,8 @@ class DeviceFingerprint { * Информация о навигаторе */ async collectNavigatorInfo() { + if (!isBrowser) return; + try { this.components.set('navigator', { userAgent: navigator.userAgent, @@ -93,6 +108,8 @@ class DeviceFingerprint { * Canvas fingerprinting */ async collectCanvasFingerprint() { + if (!isBrowser) return; + try { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); @@ -116,7 +133,7 @@ class DeviceFingerprint { ctx.fill(); const canvasData = canvas.toDataURL(); - this.components.set('canvas', this.hashString(canvasData)); + this.components.set('canvas', await this.hashString(canvasData)); } catch (e) { console.warn('[DeviceFingerprint] Не удалось создать canvas fingerprint:', e); @@ -127,6 +144,8 @@ class DeviceFingerprint { * WebGL fingerprinting */ async collectWebGLFingerprint() { + if (!isBrowser) return; + try { const canvas = document.createElement('canvas'); const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); @@ -152,6 +171,8 @@ class DeviceFingerprint { * Audio fingerprinting */ async collectAudioFingerprint() { + if (!isBrowser) return Promise.resolve(); + return new Promise((resolve) => { try { if (!window.AudioContext && !window.webkitAudioContext) { @@ -177,11 +198,11 @@ class DeviceFingerprint { scriptProcessor.connect(gainNode); gainNode.connect(context.destination); - scriptProcessor.onaudioprocess = (event) => { + scriptProcessor.onaudioprocess = async (event) => { const bins = new Float32Array(analyser.frequencyBinCount); analyser.getFloatFrequencyData(bins); - const audioHash = this.hashString(bins.slice(0, 50).join(',')); + const audioHash = await this.hashString(bins.slice(0, 50).join(',')); this.components.set('audio', audioHash); oscillator.disconnect(); @@ -211,6 +232,8 @@ class DeviceFingerprint { * Информация о временной зоне */ async collectTimezoneInfo() { + if (!isBrowser) return; + try { this.components.set('timezone', { timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, @@ -226,6 +249,8 @@ class DeviceFingerprint { * Языковые настройки */ async collectLanguageInfo() { + if (!isBrowser) return; + try { this.components.set('language', { language: navigator.language, @@ -240,6 +265,8 @@ class DeviceFingerprint { * Платформа и ОС */ async collectPlatformInfo() { + if (!isBrowser) return; + try { this.components.set('platform', { platform: navigator.platform, @@ -255,6 +282,8 @@ class DeviceFingerprint { * Аппаратная информация */ async collectHardwareInfo() { + if (!isBrowser) return; + try { this.components.set('hardware', { hardwareConcurrency: navigator.hardwareConcurrency || 0, @@ -270,6 +299,8 @@ class DeviceFingerprint { * Информация о хранилище */ async collectStorageInfo() { + if (!isBrowser) return; + try { let storageQuota = 0; if ('storage' in navigator && 'estimate' in navigator.storage) { @@ -292,6 +323,8 @@ class DeviceFingerprint { * Сетевая информация */ async collectNetworkInfo() { + if (!isBrowser) return; + try { const networkInfo = {}; @@ -312,6 +345,8 @@ class DeviceFingerprint { * Генерация финального отпечатка */ async generateFingerprint() { + if (!isBrowser) return 'server-environment-no-fingerprint'; + const componentsString = JSON.stringify(Array.from(this.components.entries()).sort()); return await this.hashString(componentsString); } @@ -320,6 +355,8 @@ class DeviceFingerprint { * Создание резервного отпечатка при ошибках */ async createFallbackFingerprint() { + if (!isBrowser) return 'server-environment-no-fingerprint'; + const fallbackData = { userAgent: navigator.userAgent, screen: `${screen.width}x${screen.height}`, @@ -335,12 +372,19 @@ class DeviceFingerprint { * Хеширование строки */ async hashString(str) { - if (crypto && crypto.subtle) { - const encoder = new TextEncoder(); - const data = encoder.encode(str); - const hashBuffer = await crypto.subtle.digest('SHA-256', data); - const hashArray = Array.from(new Uint8Array(hashBuffer)); - return hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); + if (!isBrowser) return 'server-environment-hash'; + + if (window.crypto && window.crypto.subtle) { + try { + const encoder = new TextEncoder(); + const data = encoder.encode(str); + const hashBuffer = await window.crypto.subtle.digest('SHA-256', data); + const hashArray = Array.from(new Uint8Array(hashBuffer)); + return hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); + } catch (e) { + console.warn('[DeviceFingerprint] Ошибка при использовании Crypto API:', e); + return this.simpleHash(str); + } } else { // Fallback для старых браузеров return this.simpleHash(str); diff --git a/src/views/DeviceBlockedView.vue b/src/views/DeviceBlockedView.vue new file mode 100644 index 0000000..4860eab --- /dev/null +++ b/src/views/DeviceBlockedView.vue @@ -0,0 +1,95 @@ + + + + + \ No newline at end of file