SHA256
230 lines
15 KiB
JavaScript
230 lines
15 KiB
JavaScript
import { renderHeader } from '../components/header.js';
|
|
import { state } from '../state.js';
|
|
|
|
export const pageMeta = { id: 'registration-faq-view', title: 'Вопросы о регистрации', showAppChrome: false };
|
|
|
|
export const REGISTRATION_FAQ_TOPICS = [
|
|
{
|
|
id: 'keys-storage',
|
|
shortTitle: 'Где ключи',
|
|
title: 'У кого хранятся ключи?',
|
|
paragraphs: [
|
|
'Ключи хранятся только у вас: на вашем устройстве, на доверенных устройствах или на отдельном внешнем устройстве, которое вы контролируете сами.',
|
|
'SHiNE не хранит ваши приватные ключи на сервере. Сервер помогает с доставкой и синхронизацией, но не владеет вашим секретом.',
|
|
'Если захотите, ключи можно держать на отдельном полностью программируемом устройстве с открытым кодом, например на ESP32-контроллере.',
|
|
],
|
|
},
|
|
{
|
|
id: 'reliability',
|
|
shortTitle: 'Надёжность',
|
|
title: 'Насколько это надёжно?',
|
|
paragraphs: [
|
|
'Мы делаем ставку на открытость: клиентский код открыт, серверный код открыт, протокол открыт. Это позволяет проверять систему независимо, а не верить обещаниям на слово.',
|
|
'Мы рекомендуем использовать браузеры с открытым исходным кодом. Позже планируются отдельные приложения для Android, iPhone, Ubuntu Touch и Linux, тоже с открытым кодом.',
|
|
'Проект распространяется по лицензии AGPL v3. Часть важных данных и регистрационных записей также опирается на блокчейн-слой, чтобы уменьшать зависимость от одной закрытой стороны.',
|
|
],
|
|
},
|
|
{
|
|
id: 'key-derivation',
|
|
shortTitle: 'Деривация',
|
|
title: 'Как генерируются ключи и что делает пароль?',
|
|
paragraphs: [
|
|
'Из вашего логина и пароля с помощью Argon2id вычисляется специальный секрет.',
|
|
'Уже из этого секрета детерминированно строятся три основных ключа: root key, blockchain key и device key.',
|
|
'Это значит, что логин и пароль не просто проверяются на сервере, а реально участвуют в создании ваших ключей. У разных логинов даже с одинаковым паролем будут разные ключи.',
|
|
],
|
|
},
|
|
{
|
|
id: 'three-keys',
|
|
shortTitle: 'Три ключа',
|
|
title: 'Зачем нужны три ключа?',
|
|
paragraphs: [
|
|
'Root key нужен для управления вашей основной публичной записью и важными изменениями личности, включая обновление главной публичной части в Solana.',
|
|
'Blockchain key нужен для подписания действий и записей в блокчейне SHiNE.',
|
|
'Device key нужен для входов и работы конкретного устройства. Благодаря разделению ключей можно точнее выдавать права одним устройствам и не выдавать другим.',
|
|
'Если не хочется в это вникать, обычно можно просто сохранить все ключи на своём устройстве. Для большинства обычных сценариев на iPhone, Android и Linux это вполне практично. Для больших сумм или повышенного риска лучше отдельное внешнее устройство.',
|
|
],
|
|
},
|
|
{
|
|
id: 'generation-time',
|
|
shortTitle: 'Зачем время',
|
|
title: 'Зачем нужна заметная пауза при генерации?',
|
|
paragraphs: [
|
|
'Генерация специально сделана не мгновенной. Это усложняет массовый подбор паролей.',
|
|
'Argon2id расходует и время, и память, поэтому атаки на GPU и видеокартах становятся заметно дороже и медленнее.',
|
|
'Небольшая задержка при создании секрета здесь работает как дополнительная защита.',
|
|
],
|
|
},
|
|
{
|
|
id: 'strong-password',
|
|
shortTitle: 'Какой пароль',
|
|
title: 'Какой пароль считается надёжным?',
|
|
paragraphs: [
|
|
'Минимально разумный уровень сейчас начинается примерно от 8 символов.',
|
|
'Хороший практический ориентир для большинства людей: 12 символов и больше. Пароль у нас может быть длиной до 256 символов.',
|
|
'Если вам удобнее думать словами, можно использовать режим из 12 полей ниже: слова просто склеиваются в один длинный пароль, и система не проверяет орфографию.',
|
|
],
|
|
},
|
|
{
|
|
id: 'one-or-twelve',
|
|
shortTitle: '1 или 12 слов',
|
|
title: 'Чем отличается один пароль от режима 12 слов?',
|
|
paragraphs: [
|
|
'Технически ничем: это один и тот же пароль. Режим 12 слов нужен только для удобства запоминания и ввода.',
|
|
'Можно заполнить все 12 полей, можно только первые 6, можно использовать слова от другого кошелька, разные языки и любые нестандартные символы.',
|
|
'Главное помнить, что в конце всё равно получается одна строка длиной до 256 символов.',
|
|
],
|
|
},
|
|
{
|
|
id: 'why-own-password',
|
|
shortTitle: 'Зачем свой',
|
|
title: 'Почему лучше иметь свой пароль и свои ключи?',
|
|
paragraphs: [
|
|
'Чем дальше, тем проще будет подделывать фотографию, голос, интонацию и даже другие привычные признаки личности с помощью нейросетей.',
|
|
'На расстоянии всё сложнее будет понять, что перед вами действительно вы, если опираться только на внешние признаки.',
|
|
'Поэтому персональные ключи, которые храните только вы, становятся надёжнее, чем зависимость от сторонней организации, которая держит ключи у себя.',
|
|
],
|
|
},
|
|
{
|
|
id: 'first-server',
|
|
shortTitle: 'Первый сервер',
|
|
title: 'Что такое первый сервер SHiNE?',
|
|
paragraphs: [
|
|
'Первый сервер SHiNE это тот сервер, на который вам будут писать и звонить в самом начале. При регистрации он записывается как ваш первый сервер доступа.',
|
|
'Позже вы сможете сменить сервер, а ваши данные останутся с вами. В будущем серверов может быть несколько одновременно.',
|
|
'Если серверов несколько, данные между ними будут синхронизироваться автоматически. Если добавляете новый сервер и убираете старый, просто дождитесь завершения синхронизации перед отключением старого.',
|
|
'Если у вас не остаётся ни одного сервера, синхронизации, конечно, не будет, пока не появится хотя бы один активный сервер снова.',
|
|
],
|
|
},
|
|
{
|
|
id: 'hardware-device',
|
|
shortTitle: 'ESP32',
|
|
title: 'Нужно ли отдельное устройство для ключей?',
|
|
paragraphs: [
|
|
'Идеальный вариант для важных ключей: отдельное физическое устройство, которое вы контролируете сами.',
|
|
'Если пока не хотите покупать отдельное устройство, можно пользоваться телефоном. Но отдельный контроллер или мини-устройство обычно даёт лучший контроль и более понятную модель доверия.',
|
|
'Красивая готовая модель Waveshare на ESP32-S3 Touch AMOLED 2.16 стоит около 32 долларов. Есть и более дешёвые варианты на открытых чипах, примерно от 10 до 15 долларов.',
|
|
'Если у вас другая модель, под неё можно адаптировать открытую прошивку. Для простых переносов это реально сделать довольно быстро.',
|
|
],
|
|
links: [
|
|
{
|
|
label: 'Документация Waveshare ESP32-S3 Touch AMOLED 2.16',
|
|
href: 'https://docs.waveshare.com/ESP32-S3-Touch-AMOLED-2.16',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: 'wallet-device',
|
|
shortTitle: 'Кошелёк',
|
|
title: 'Можно ли использовать такое устройство как кошелёк?',
|
|
paragraphs: [
|
|
'Да. Идея SHiNE в том, что устройство может подписывать не только внутренние действия, но и любые другие данные, если для этого добавлена нужная логика.',
|
|
'То есть это направление совместимо с моделью аппаратного кошелька: вы храните ключи у себя, а устройство подписывает то, что вы разрешили.',
|
|
'Пока ещё не все валюты и сценарии доведены до готового пользовательского уровня, но архитектурно это именно путь к универсальному подписывающему устройству.',
|
|
],
|
|
},
|
|
];
|
|
|
|
function getTopicById(topicId) {
|
|
return REGISTRATION_FAQ_TOPICS.find((topic) => topic.id === topicId) || REGISTRATION_FAQ_TOPICS[0];
|
|
}
|
|
|
|
export function openRegistrationFaq(navigate, topicId) {
|
|
state.registrationHelp.selectedTopic = getTopicById(topicId).id;
|
|
navigate('registration-faq-view');
|
|
}
|
|
|
|
export function render({ navigate }) {
|
|
const selectedTopic = getTopicById(state.registrationHelp?.selectedTopic);
|
|
const screen = document.createElement('section');
|
|
screen.className = 'stack';
|
|
|
|
const heroCard = document.createElement('div');
|
|
heroCard.className = 'card stack registration-faq-hero';
|
|
heroCard.innerHTML = `
|
|
<div class="badge alt">Вопросы о регистрации</div>
|
|
<p class="auth-copy">Короткие ответы на самые частые вопросы о ключах, пароле, первом сервере и доверенных устройствах.</p>
|
|
`;
|
|
|
|
const topicCard = document.createElement('div');
|
|
topicCard.className = 'card stack registration-faq-topic';
|
|
|
|
const question = document.createElement('h2');
|
|
question.className = 'registration-faq-title';
|
|
question.textContent = selectedTopic.title;
|
|
topicCard.append(question);
|
|
|
|
selectedTopic.paragraphs.forEach((paragraph) => {
|
|
const p = document.createElement('p');
|
|
p.className = 'auth-copy';
|
|
p.textContent = paragraph;
|
|
topicCard.append(p);
|
|
});
|
|
|
|
if (Array.isArray(selectedTopic.links) && selectedTopic.links.length > 0) {
|
|
selectedTopic.links.forEach((linkItem) => {
|
|
const link = document.createElement('a');
|
|
link.className = 'link-card';
|
|
link.href = linkItem.href;
|
|
link.target = '_blank';
|
|
link.rel = 'noopener noreferrer';
|
|
link.textContent = linkItem.label;
|
|
topicCard.append(link);
|
|
});
|
|
}
|
|
|
|
const topicsCard = document.createElement('div');
|
|
topicsCard.className = 'card stack';
|
|
|
|
const topicsLabel = document.createElement('p');
|
|
topicsLabel.className = 'field-label';
|
|
topicsLabel.textContent = 'Другие вопросы';
|
|
|
|
const topicsGrid = document.createElement('div');
|
|
topicsGrid.className = 'registration-faq-grid';
|
|
|
|
REGISTRATION_FAQ_TOPICS.forEach((topic) => {
|
|
const button = document.createElement('button');
|
|
button.className = topic.id === selectedTopic.id ? 'secondary-btn' : 'ghost-btn';
|
|
button.type = 'button';
|
|
button.textContent = topic.shortTitle;
|
|
button.addEventListener('click', () => {
|
|
state.registrationHelp.selectedTopic = topic.id;
|
|
navigate('registration-faq-view');
|
|
});
|
|
topicsGrid.append(button);
|
|
});
|
|
|
|
topicsCard.append(topicsLabel, topicsGrid);
|
|
|
|
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('register-view'));
|
|
|
|
const registerButton = document.createElement('button');
|
|
registerButton.className = 'primary-btn';
|
|
registerButton.type = 'button';
|
|
registerButton.textContent = 'К регистрации';
|
|
registerButton.addEventListener('click', () => navigate('register-view'));
|
|
|
|
actions.append(backButton, registerButton);
|
|
|
|
screen.append(
|
|
renderHeader({
|
|
title: 'Вопросы о регистрации',
|
|
leftAction: { label: '←', onClick: () => navigate('register-view') },
|
|
}),
|
|
heroCard,
|
|
topicCard,
|
|
topicsCard,
|
|
actions,
|
|
);
|
|
|
|
return screen;
|
|
}
|