SHiNE-server/shine-UI/js/pages/login-password-view.js

136 lines
5.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { renderHeader } from '../components/header.js';
import {
authService,
clearAuthMessages,
setAuthBusy,
setAuthError,
state,
} from '../state.js';
import { toUserMessage } from '../services/ui-error-texts.js';
export const pageMeta = { id: 'login-password-view', title: 'Войти по логину', showAppChrome: false };
export function render({ navigate }) {
const screen = document.createElement('section');
screen.className = 'stack';
clearAuthMessages();
const form = document.createElement('div');
form.className = 'card stack';
const loginInput = document.createElement('input');
loginInput.className = 'input';
loginInput.type = 'text';
loginInput.autocomplete = 'off';
loginInput.autocapitalize = 'off';
loginInput.spellcheck = false;
loginInput.value = state.loginDraft.login;
loginInput.placeholder = 'Введите логин';
const passwordInput = document.createElement('input');
passwordInput.className = 'input';
passwordInput.type = 'password';
passwordInput.name = 'shine-login-password';
passwordInput.autocomplete = 'new-password';
passwordInput.autocapitalize = 'off';
passwordInput.spellcheck = false;
passwordInput.value = state.loginDraft.password;
passwordInput.placeholder = 'Введите пароль (можно оставить пустым)';
const hint = document.createElement('p');
hint.className = 'meta-muted';
hint.textContent = 'Введите логин. Пароль может быть пустым. На следующем шаге сохраните ключи на устройстве.';
const advanced = document.createElement('details');
advanced.className = 'card stack';
advanced.innerHTML = `
<summary>Расширенные</summary>
<p class="meta-muted">Схема derivation ключей: логин нормализуется как `trim().toLowerCase()`. При непустом пароле используется Argon2id, затем из результата строится секрет для root/bch/dev ключей.</p>
<p class="meta-muted">Если пароль пустой — используется прежний детерминированный режим совместимости.</p>
<p class="meta-muted">Для тестов можно оставить пустой пароль.</p>
<p class="meta-muted">Профиль Argon2id сейчас фиксированный: t=2, m=65536 KiB (64 MiB), p=1, dkLen=32. Выбор уровня будет добавлен позже.</p>
`;
const status = document.createElement('p');
status.className = 'status-line is-unavailable';
status.style.display = 'none';
const testLoginsHint = document.createElement('p');
testLoginsHint.className = 'meta-muted';
testLoginsHint.textContent = 'Основные тестовые логины: 1, 2, 3 (вход без пароля).';
form.innerHTML = `
<label class="stack"><span class="field-label">Логин</span></label>
<label class="stack"><span class="field-label">Пароль</span></label>
`;
form.children[0].append(loginInput);
form.children[1].append(passwordInput);
form.append(hint, advanced, status, testLoginsHint);
const actions = document.createElement('div');
actions.className = 'auth-footer-actions';
const backButton = document.createElement('button');
backButton.className = 'ghost-btn';
backButton.type = 'button';
backButton.textContent = 'Назад';
backButton.addEventListener('click', () => navigate('start-view'));
const enterButton = document.createElement('button');
enterButton.className = 'primary-btn';
enterButton.type = 'button';
enterButton.textContent = 'Войти';
enterButton.addEventListener('click', async () => {
status.style.display = 'none';
state.loginDraft.login = loginInput.value.trim();
state.loginDraft.password = passwordInput.value;
if (!state.loginDraft.login) {
status.textContent = 'Введите логин.';
status.style.display = '';
return;
}
setAuthBusy(true);
setAuthError('');
enterButton.disabled = true;
enterButton.textContent = 'Входим...';
try {
await authService.reconnect(state.entrySettings.shineServer);
const result = await authService.createSessionForExistingUser(state.loginDraft.login, state.loginDraft.password);
state.registrationDraft.flowType = 'login';
state.registrationDraft.login = result.login;
state.registrationDraft.password = state.loginDraft.password;
state.registrationDraft.sessionId = result.sessionId;
state.registrationDraft.storagePwd = result.storagePwd;
state.registrationDraft.pendingKeyBundle = result.keyBundle;
state.registrationDraft.pendingSessionMaterial = result.sessionMaterial;
navigate('registration-keys-view');
} catch (error) {
const message = toUserMessage(error, 'Не удалось выполнить вход.');
setAuthError(message);
status.textContent = message;
status.style.display = '';
} finally {
setAuthBusy(false);
enterButton.disabled = false;
enterButton.textContent = 'Войти';
}
});
actions.append(backButton, enterButton);
screen.append(
renderHeader({
title: 'Войти по логину',
leftAction: { label: '←', onClick: () => navigate('start-view') },
}),
form,
actions,
);
return screen;
}