import { renderHeader } from '../components/header.js'; import { authService, setAuthError, setAuthInfo, state } from '../state.js'; import { deriveEspPairingPasswordHash } from '../services/device-pairing-service.js'; import { toUserMessage } from '../services/ui-error-texts.js'; export const pageMeta = { id: 'trusted-device-login-settings-view', title: 'Настройки входа через устройство' }; function setStatus(statusEl, message, kind = 'info') { statusEl.classList.toggle('is-unavailable', kind === 'error'); statusEl.classList.toggle('is-available', kind !== 'error'); statusEl.textContent = message; statusEl.style.display = message ? '' : 'none'; } function describeState(settings) { if (!settings?.enabled) return 'Вход через другое устройство запрещён.'; if (settings?.hasPassword) return 'Вход через другое устройство разрешён только с дополнительным паролем.'; return 'Вход через другое устройство разрешён без дополнительного пароля.'; } export function render({ navigate }) { const screen = document.createElement('section'); screen.className = 'stack'; const card = document.createElement('div'); card.className = 'card stack'; const summary = document.createElement('p'); summary.className = 'auth-copy'; summary.textContent = 'Загружаем текущие настройки...'; const hint = document.createElement('p'); hint.className = 'meta-muted'; hint.textContent = 'Дополнительный пароль не даёт права на вход сам по себе. Он только отсекает лишние заявки до подтверждения на доверенном устройстве.'; const status = document.createElement('p'); status.className = 'status-line is-unavailable'; status.style.display = 'none'; const actions = document.createElement('div'); actions.className = 'row'; actions.style.flexWrap = 'wrap'; const enableToggleBtn = document.createElement('button'); enableToggleBtn.className = 'primary-btn'; enableToggleBtn.type = 'button'; const noPasswordBtn = document.createElement('button'); noPasswordBtn.className = 'ghost-btn'; noPasswordBtn.type = 'button'; noPasswordBtn.textContent = 'Сделать вход без пароля'; const passwordForm = document.createElement('div'); passwordForm.className = 'stack'; passwordForm.innerHTML = ` `; const passwordInput = passwordForm.querySelector('#trusted-login-password'); const passwordConfirmInput = passwordForm.querySelector('#trusted-login-password-confirm'); const savePasswordBtn = passwordForm.querySelector('#trusted-login-password-save'); card.append(summary, hint, actions, passwordForm, status); let settings = { enabled: true, hasPassword: false }; let busy = false; const setBusy = (flag) => { busy = flag; enableToggleBtn.disabled = flag; noPasswordBtn.disabled = flag || !settings.enabled || !settings.hasPassword; savePasswordBtn.disabled = flag; passwordInput.disabled = flag; passwordConfirmInput.disabled = flag; }; const renderUi = () => { summary.textContent = describeState(settings); enableToggleBtn.textContent = settings.enabled ? 'Запретить вход через другое устройство' : 'Разрешить вход через другое устройство'; actions.innerHTML = ''; actions.append(enableToggleBtn); if (settings.enabled) { actions.append(noPasswordBtn); } passwordForm.style.display = settings.enabled ? '' : 'none'; noPasswordBtn.disabled = busy || !settings.hasPassword; setBusy(busy); }; const reloadSettings = async () => { settings = await authService.getTrustedDeviceLoginSettings(); renderUi(); }; enableToggleBtn.addEventListener('click', async () => { setStatus(status, '', 'info'); setBusy(true); try { settings = await authService.upsertTrustedDeviceLoginSettings({ enabled: !settings.enabled, passwordHash: '', }); renderUi(); setAuthInfo(settings.enabled ? 'Вход через другое устройство разрешён.' : 'Вход через другое устройство запрещён.'); setStatus(status, describeState(settings), 'info'); } catch (error) { const message = toUserMessage(error, 'Не удалось изменить режим входа.'); setAuthError(message); setStatus(status, message, 'error'); } finally { setBusy(false); } }); noPasswordBtn.addEventListener('click', async () => { setStatus(status, '', 'info'); setBusy(true); try { settings = await authService.upsertTrustedDeviceLoginSettings({ enabled: true, passwordHash: '', }); renderUi(); setAuthInfo('Вход через другое устройство теперь работает без дополнительного пароля.'); setStatus(status, 'Вход теперь работает без дополнительного пароля.', 'info'); } catch (error) { const message = toUserMessage(error, 'Не удалось убрать дополнительный пароль.'); setAuthError(message); setStatus(status, message, 'error'); } finally { setBusy(false); } }); savePasswordBtn.addEventListener('click', async () => { setStatus(status, '', 'info'); const password = String(passwordInput.value || ''); const confirm = String(passwordConfirmInput.value || ''); if (!password || !confirm) { setStatus(status, 'Заполните пароль и подтверждение.', 'error'); return; } if (password !== confirm) { setStatus(status, 'Пароли не совпадают.', 'error'); return; } setBusy(true); try { const finalHash = await deriveEspPairingPasswordHash( String(state.session.login || ''), password, ); settings = await authService.upsertTrustedDeviceLoginSettings({ enabled: true, passwordHash: finalHash, }); passwordInput.value = ''; passwordConfirmInput.value = ''; renderUi(); setAuthInfo('Дополнительный пароль для входа через другое устройство сохранён.'); setStatus(status, 'Дополнительный пароль сохранён.', 'info'); } catch (error) { const message = toUserMessage(error, 'Не удалось сохранить дополнительный пароль.'); setAuthError(message); setStatus(status, message, 'error'); } finally { setBusy(false); } }); screen.append( renderHeader({ title: 'Настройки входа через устройство', leftAction: { label: '←', onClick: () => navigate('device-pairing-view') }, }), card, ); void reloadSettings().catch((error) => { const message = toUserMessage(error, 'Не удалось загрузить настройки входа через устройство.'); setAuthError(message); setStatus(status, message, 'error'); }); return screen; }