let currentUser = null; function getCookie(name) { const match = document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]+)'); return match ? match[2] : null; } function setCookie(name, value, maxAgeSec) { document.cookie = name + '=' + value + ';path=/;max-age=' + maxAgeSec; } function getLockedUntil() { return parseInt(getCookie('lockout_until') || '0', 10); } function setLockedUntil(ts) { setCookie('lockout_until', ts, 300); } function getLoginAttempts() { return parseInt(getCookie('login_attempts') || '0', 10); } function setLoginAttempts(count) { setCookie('login_attempts', count, 300); } function clearLoginCookies() { setCookie('login_attempts', 0, 0); setCookie('lockout_until', 0, 0); } function isLocked() { return Date.now() < getLockedUntil(); } function getRemainingLockoutSeconds() { const rem = Math.floor((getLockedUntil() - Date.now()) / 1000); return Math.max(0, rem); } async function checkAuth() { try { const response = await fetch('/api/user', { credentials: 'include' }); if (response.ok) { const data = await response.json(); currentUser = data.user; if (data.csrfToken) { window.__CSRF_TOKEN = data.csrfToken; } showMainInterface(); } else { showLoginInterface(); } } catch (error) { showLoginInterface(); } } function showLoginInterface() { document.getElementById('app').style.display = 'none'; document.getElementById('login-screen').style.display = 'flex'; } function showMainInterface() { document.getElementById('login-screen').style.display = 'none'; document.getElementById('app').style.display = 'block'; const userEl = document.getElementById('currentUser'); if (userEl && currentUser) { userEl.textContent = (currentUser.role === 'admin' ? 'πŸ‘‘ ' : '') + currentUser.name; } if (typeof loadFilters === 'function') loadFilters(); if (typeof loadAll === 'function') loadAll(); } async function login(event) { event.preventDefault(); if (isLocked()) { const rem = getRemainingLockoutSeconds(); document.getElementById('login-error').textContent = 'Блишком ΠΌΠ½ΠΎΠ³ΠΎ ΠΏΠΎΠΏΡ‹Ρ‚ΠΎΠΊ. ΠŸΠΎΠ΄ΠΎΠΆΠ΄ΠΈΡ‚Π΅ ' + rem + ' сСкунд.'; document.getElementById('login-error').style.display = 'block'; document.getElementById('login-attempts').textContent = ''; return; } const login = document.getElementById('login').value; const password = document.getElementById('password').value; const errorEl = document.getElementById('login-error'); const attemptsEl = document.getElementById('login-attempts'); try { const response = await fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ login, password }) }); if (response.ok) { clearLoginCookies(); const data = await response.json(); currentUser = data.user; if (data.csrfToken) { window.__CSRF_TOKEN = data.csrfToken; } showMainInterface(); } else if (response.status === 429) { const data = await response.json(); errorEl.textContent = data.error; errorEl.style.display = 'block'; } else { let attempts = getLoginAttempts() + 1; setLoginAttempts(attempts); if (attempts >= 10) { setLockedUntil(Date.now() + 300000); errorEl.textContent = 'Блишком ΠΌΠ½ΠΎΠ³ΠΎ ΠΏΠΎΠΏΡ‹Ρ‚ΠΎΠΊ. ΠŸΠΎΠ΄ΠΎΠΆΠ΄ΠΈΡ‚Π΅ 5 ΠΌΠΈΠ½ΡƒΡ‚.'; attemptsEl.textContent = ''; } else { errorEl.textContent = 'НСвСрный Π»ΠΎΠ³ΠΈΠ½ ΠΈΠ»ΠΈ ΠΏΠ°Ρ€ΠΎΠ»ΡŒ'; attemptsEl.textContent = 'ΠŸΠΎΠΏΡ‹Ρ‚ΠΎΠΊ: ' + attempts + ' ΠΈΠ· 10'; } errorEl.style.display = 'block'; } } catch (error) { document.getElementById('login-error').textContent = 'Ошибка соСдинСния'; document.getElementById('login-error').style.display = 'block'; } } async function logout() { try { await fetch('/api/logout', { method: 'POST' }); } catch (e) {} currentUser = null; window.__CSRF_TOKEN = null; showLoginInterface(); } function togglePasswordVisibility() { const pwd = document.getElementById('password'); const icon = document.querySelector('.password-toggle i'); if (pwd.type === 'password') { pwd.type = 'text'; if (icon) icon.className = 'fas fa-eye-slash'; } else { pwd.type = 'password'; if (icon) icon.className = 'fas fa-eye'; } }