фикс
This commit is contained in:
parent
2317ecafca
commit
e8c839d493
33
src/main.js
33
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
|
||||
|
@ -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] Сервис уничтожен');
|
||||
}
|
||||
}
|
||||
|
||||
// Создаем единственный экземпляр сервиса
|
||||
|
@ -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);
|
||||
|
95
src/views/DeviceBlockedView.vue
Normal file
95
src/views/DeviceBlockedView.vue
Normal file
@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<div class="device-blocked-container">
|
||||
<div class="blocked-card">
|
||||
<div class="blocked-icon">
|
||||
<i class="fas fa-ban"></i>
|
||||
</div>
|
||||
<h1>Устройство заблокировано</h1>
|
||||
<p>
|
||||
Для безопасности вашей учетной записи, это устройство было заблокировано администратором.
|
||||
</p>
|
||||
<p class="device-info" v-if="fingerprint">
|
||||
ID устройства: <code>{{ shortenFingerprint(fingerprint) }}</code>
|
||||
</p>
|
||||
<p class="contact-info">
|
||||
Если вы считаете, что это ошибка, пожалуйста, свяжитесь с администратором сервиса.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useAuth } from '../auth';
|
||||
|
||||
const { deviceFingerprint } = useAuth();
|
||||
const fingerprint = ref('');
|
||||
|
||||
onMounted(() => {
|
||||
fingerprint.value = deviceFingerprint.value || localStorage.getItem('deviceFingerprint') || '';
|
||||
});
|
||||
|
||||
const shortenFingerprint = (fp) => {
|
||||
if (!fp) return '';
|
||||
if (fp.length <= 16) return fp;
|
||||
return fp.substring(0, 8) + '...' + fp.substring(fp.length - 8);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.device-blocked-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.blocked-card {
|
||||
max-width: 500px;
|
||||
padding: 30px;
|
||||
border-radius: 10px;
|
||||
background-color: white;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.blocked-icon {
|
||||
font-size: 64px;
|
||||
color: #dc3545;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #dc3545;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 15px;
|
||||
color: #555;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.device-info {
|
||||
background-color: #f8f9fa;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: monospace;
|
||||
background-color: #eee;
|
||||
padding: 3px 5px;
|
||||
border-radius: 3px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.contact-info {
|
||||
font-style: italic;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
Loading…
x
Reference in New Issue
Block a user