разработана аутентификация и регистрация аккаунта

This commit is contained in:
2023-03-08 21:43:33 +10:00
parent 0d6ceef0f9
commit 87bd15640a
24 changed files with 1082 additions and 1353 deletions

View File

@@ -1,247 +0,0 @@
{% block css %}
<link type="text/css" rel="stylesheet" href="/css/account.css">
<link type="text/css" rel="stylesheet" href="/css/icons/arrow_right.css">
<link type="text/css" rel="stylesheet" href="/css/icons/nametag.css">
<link type="text/css" rel="stylesheet" href="/css/icons/keyhole.css">
<link type="text/css" rel="stylesheet" href="/css/icons/user_add.css">
{% endblock %}
{% block body %}
<section id="entry" class="panel medium">
<section class="header unselectable">
<h1>Идентификация</h1>
</section>
<section class="body">
<label id="login">
<i class="nametag"></i>
<input name="login" type="text" placeholder="Входной псевдоним"
value="{{ account.login ?? session.buffer.login ?? cookie.buffer_login }}"
onkeypress="if (event.keyCode === 13) _login()" autofocus>
<button class="accept" onclick="_login()"><i class="arrow right"></i></button>
</label>
<label id="password" class="hidden">
<i class="keyhole"></i>
<input name="password" type="password" placeholder="Пароль" autocomplete="current-password"
onkeypress="if (event.keyCode === 13) _password()">
<button class="accept" onclick="_password()"><i class="arrow right"></i></button>
</label>
<label id="invite" class="hidden">
<i class="user add"></i>
<input name="invite" type="text" placeholder="Ключ приглашения" onkeypress="if (event.keyCode === 13) _invite()">
<button class="accept" onclick="_invite()"><i class="arrow right"></i></button>
</label>
</section>
</section>
<section id="mnemonic" class="panel small hidden">
<section class="header unselectable">
<h2>Мнемонические</h2>
</section>
<section class="body">
<ul></ul>
</section>
</section>
<section id="classic" class="panel small hidden">
<section class="header unselectable">
<h2>Классические</h2>
</section>
<section class="body">
<ul></ul>
</section>
</section>
<script>
// Инициализация реестра ошибок
let errors = new Map;
// Инициализация функций в глобальной области видимости
let _login, _password, _invite
document.addEventListener('damper.initialized', function (e) {
// Инициализирован демпфер
// Инициализация узлов
const entry = document.getElementById('entry');
const mnemonic = document.getElementById('mnemonic');
const classic = document.getElementById('classic');
// Инициализация элемента с заголовком
const title = entry.getElementsByTagName('h1')[0];
// Инициализация элементов-оболочек полей ввода
const labels = {
login: document.getElementById('login'),
invite: document.getElementById('invite'),
password: document.getElementById('password')
};
/**
* Отправить входной псевдоним на сервер
*
* @return {void}
*/
_login = () => {
// Инициализация поля ввода
const input = labels.login.querySelector('input[name=login]');
// Блокировка поля ввода
input.disabled = true;
return e.detail.damper(async () => {
// Запрос к серверу
const response = await session.login(input.value, errors);
if (response.exist) {
// Найден аккаунт
// Инициализация интерфейса аутентификации
title.innerText = 'Аутентификация';
labels.login.classList.add('hidden');
labels.password.classList.remove('hidden');
labels.password.querySelector('input[name=password]').focus();
} else {
// Не найден аккаунт
// Инициализация интерфейса регистрации
title.innerText = 'Регистрация';
labels.login.classList.add('hidden');
labels.invite.classList.remove('hidden');
labels.invite.querySelector('input[name=invite]').focus();
}
}, 1000)();
};
/**
* Отправить пароль на сервер
*
* @return {void}
*/
_password = () => {
// Инициализация поля ввода
const input = labels.password.querySelector('input[name=password]');
// Блокировка поля ввода
input.disabled = true;
return e.detail.damper(async () => {
// Деинициализация индикатора и анимации об ошибке
input.classList.remove('error');
// Запрос к серверу
const response = await session.password(input.value, errors);
if (response.verify) {
// Пройдена проверка пароля на соответствие требованиям
} else {
// Не пройдена проверка пароля на соответствие требованиям
// Разблокировка поля для ввода
input.disabled = false;
// Инициализация отображения ошибки
input.classList.add('error');
// Фокусировка на поле ввода
input.focus();
}
}, 1000)();
}
/**
* Отправить код приглашения на сервер
*
* @return {void}
*/
_invite = () => {
// Инициализация поля ввода
const input = labels.invite.querySelector('input[name=invite]');
// Блокировка поля ввода
input.disabled = true;
return e.detail.damper(async () => {
// Деинициализация индикатора и анимации об ошибке
input.classList.remove('error');
// Запрос к серверу
const response = await session.invite(input.value, errors);
if (response.exist) {
// Найдено приглашение
// Инициализация интерфейса ввода пароля
labels.invite.classList.add('hidden');
labels.password.querySelector('input[name=password]').autocomplete = 'new-password';
labels.password.classList.remove('hidden');
mnemonic.classList.remove('hidden');
classic.classList.remove('hidden');
for (let i = 0; i < 6; i++) {
// Генерация HTML-элементов с примерами мнемонических паролей
// Инициализация HTML-элемента
const element = document.createElement('li');
// Генерация пароля
password.generate(Math.floor(Math.random() * 3) + 2, 'mnemonic')
.then((responce) => {
if (responce.password === '') {
// Не удалось сгенерировать пароль
// Перезапуск итерации
--i;
return;
}
// Запись пароля
element.innerText = responce.password;
// Запись HTML-элемента с паролем в список
mnemonic.getElementsByTagName('ul')[0].appendChild(element);
});
}
for (let i = 0; i < 8; i++) {
// Генерация HTML-элементов с примерами классических паролей
// Инициализация HTML-элемента
const element = document.createElement('li');
// Генерация пароля
password.generate(Math.floor(Math.random() * 14) + 4)
.then((responce) => {
if (responce.password === '') {
// Не удалось сгенерировать пароль
// Перезапуск итерации
--i;
return;
}
// Запись пароля
element.innerText = responce.password;
// Запись HTML-элемента с паролем в список
classic.getElementsByTagName('ul')[0].appendChild(element);
});
}
} else {
// Не найдено приглашение
// Разблокировка поля для ввода
input.disabled = false;
// Инициализация отображения ошибки
input.classList.add('error');
// Фокусировка на поле ввода
input.focus();
}
}, 1000)();
};
});
</script>
{% endblock %}
{% block js %}
<script type="text/javascript" src="/js/session.js"></script>
<script type="text/javascript" src="/js/password.js"></script>
{% endblock %}

View File

@@ -0,0 +1,410 @@
{% block css %}
<link type="text/css" rel="stylesheet" href="/css/account.css">
<link type="text/css" rel="stylesheet" href="/css/icons/arrow_right.css">
<link type="text/css" rel="stylesheet" href="/css/icons/nametag.css">
<link type="text/css" rel="stylesheet" href="/css/icons/keyhole.css">
<link type="text/css" rel="stylesheet" href="/css/icons/user_add.css">
{% endblock %}
{% block body %}
<div class="column">
<section id="entry" class="panel medium">
<section class="header unselectable">
<h1>Идентификация</h1>
</section>
<section class="body">
<label id="login">
<i class="nametag"></i>
<input name="login" type="text" placeholder="Входной псевдоним"
value="{{ account.login ?? session.buffer.login ?? cookie.buffer_login }}"
onkeypress="if (event.keyCode === 13) __login()" autofocus>
<button class="accept" onclick="__login()"><i class="arrow right"></i></button>
</label>
<label id="password" class="hidden">
<i class="keyhole"></i>
<input name="password" type="password" placeholder="Пароль" autocomplete="current-password"
onkeypress="if (event.keyCode === 13) __password()">
<button class="accept" onclick="__password()"><i class="arrow right"></i></button>
</label>
<label id="invite" class="hidden">
<i class="user add"></i>
<input name="invite" type="text" placeholder="Ключ приглашения"
onkeypress="if (event.keyCode === 13) __invite()">
<button class="accept" onclick="__invite()"><i class="arrow right"></i></button>
</label>
</section>
</section>
<section id="errors" class="panel medium animation window hidden" style="--height: 300px">
<section class="body">
<dl></dl>
</section>
</section>
</div>
<section id="mnemonic" class="panel small hidden">
<section class="header unselectable">
<h2>Мнемонические</h2>
</section>
<section class="body">
<ul></ul>
</section>
</section>
<section id="classic" class="panel small hidden">
<section class="header unselectable">
<h2>Классические</h2>
</section>
<section class="body">
<ul></ul>
</section>
</section>
<script>
// Инициализация функций в глобальной области видимости
let _login, __login, _password, __password, _invite, __invite, _errors;
document.addEventListener('damper.initialized', function (e) {
// Инициализирован демпфер
// Инициализация HTML-элемента с блоком входа в аккаунт
const entry = {wrap: document.getElementById('entry')};
entry.title = entry.wrap.getElementsByTagName('h1')[0];
// Инициализация HTML-элемента с блоком мнемонических паролей
const mnemonic = {wrap: document.getElementById('mnemonic')};
mnemonic.list = mnemonic.wrap.getElementsByTagName('ul')[0];
// Инициализация HTML-элемента с блоком классических паролей
const classic = {wrap: document.getElementById('classic')};
classic.list = classic.wrap.getElementsByTagName('ul')[0];
// Инициализация HTML-элемента с блоком ошибок
const errors = {wrap: document.getElementById('errors')};
errors.list = errors.wrap.getElementsByTagName('dl')[0];
// Инициализация HTML-элементов-оболочек полей ввода
const labels = {
login: {label: document.getElementById('login')},
password: {label: document.getElementById('password')},
invite: {label: document.getElementById('invite')}
};
labels.login.input = labels.login.label.querySelector('input[name=login]');
labels.password.input = labels.password.label.querySelector('input[name=password]');
labels.invite.input = labels.invite.label.querySelector('input[name=invite]');
/**
* Отправить входной псевдоним на сервер
*
* @return {void}
*/
_login = e.detail.damper(async () => {
// Деинициализация индикатора и анимации об ошибке
labels.login.input.classList.remove('error');
// Запрос к серверу
const response = await session.login(labels.login.input.value);
if (_errors(response.errors)) {
// Сгенерированы ошибки
// Разлокировка поля ввода
labels.login.input.disabled = false;;
// Инициализация отображения ошибки
labels.login.input.classList.add('error');
// Фокусировка на поле ввода
labels.login.input.focus();
} else {
// Не сгенерированы ошибки (подразумевается их отсутствие)
if (typeof response.exist === 'boolean')
if (response.exist) {
// Найден аккаунт
// Инициализация интерфейса аутентификации
entry.title.innerText = 'Аутентификация';
labels.login.label.classList.add('hidden');
labels.password.label.classList.remove('hidden');
labels.password.input.focus();
} else {
// Не найден аккаунт
// Инициализация интерфейса регистрации
entry.title.innerText = 'Регистрация';
labels.login.label.classList.add('hidden');
labels.invite.label.classList.remove('hidden');
labels.invite.input.focus();
}
}
}, 500);
/**
* Отправить входной псевдоним на сервер с дополнительной подготовкой
*
* @return {void}
*/
__login = () => {
// Блокировка поля ввода
labels.login.input.disabled = true;
// Реинициализация блока ошибок
_errors();
// Запуск процесса отправки входного псевдонима
_login();
}
/**
* Отправить пароль на сервер
*
* @return {void}
*/
_password = e.detail.damper(async () => {
// Деинициализация индикатора и анимации об ошибке
labels.password.input.classList.remove('error');
// Запрос к серверу
const response = await session.password(labels.password.input.value);
if (_errors(response.errors)) {
// Сгенерированы ошибки
// Разлокировка поля ввода
labels.password.input.disabled = false;
// Инициализация отображения ошибки
labels.password.input.classList.add('error');
// Фокусировка на поле ввода
labels.password.input.focus();
} else {
// Не сгенерированы ошибки (подразумевается их отсутствие)
if (typeof response.verify === 'boolean')
if (response.verify) {
// Пройдена проверка пароля на соответствие требованиям
if (response.account) {
// Инициализирован аккаунт
}
}
}
}, 500);
/**
* Отправить пароль на сервер с дополнительной подготовкой
*
* @return {void}
*/
__password = () => {
// Блокировка поля ввода
labels.password.input.disabled = true;
// Реинициализация блока ошибок
_errors();
// Запуск процесса отправки входного псевдонима
_password();
}
/**
* Отправить код приглашения на сервер
*
* @return {void}
*/
_invite = e.detail.damper(async () => {
// Деинициализация индикатора и анимации об ошибке
labels.invite.input.classList.remove('error');
// Запрос к серверу
const response = await session.invite(labels.invite.input.value);
if (_errors(response.errors)) {
// Сгенерированы ошибки
// Разблокировка поля ввода
labels.invite.input.disabled = false;
// Инициализация отображения ошибки
labels.invite.input.classList.add('error');
// Фокусировка на поле ввода
labels.invite.input.focus();
} else {
// Не сгенерированы ошибки (подразумевается их отсутствие)
if (typeof response.exist === 'boolean')
if (response.exist) {
// Найдено приглашение
// Инициализация интерфейса ввода пароля
labels.invite.label.classList.add('hidden');
labels.password.input.autocomplete = 'new-password';
labels.password.label.classList.remove('hidden');
mnemonic.wrap.classList.remove('hidden');
classic.wrap.classList.remove('hidden');
for (let i = 0; i < 6; i++) {
// Генерация HTML-элементов с примерами мнемонических паролей
// Инициализация HTML-элемента
const element = document.createElement('li');
// Генерация пароля
password.generate(Math.floor(Math.random() * 3) + 2, 'mnemonic')
.then((responce) => {
if (responce.password === '') {
// Не удалось сгенерировать пароль
// Перезапуск итерации
--i;
return;
}
// Запись пароля
element.innerText = responce.password;
// Запись HTML-элемента с паролем в список
mnemonic.list.appendChild(element);
// Генерация списка с текстом ошибок
_errors(response.errors);
});
}
for (let i = 0; i < 8; i++) {
// Генерация HTML-элементов с примерами классических паролей
// Инициализация HTML-элемента
const element = document.createElement('li');
// Генерация пароля
password.generate(Math.floor(Math.random() * 14) + 4)
.then((responce) => {
if (responce.password === '') {
// Не удалось сгенерировать пароль
// Перезапуск итерации
--i;
return;
}
// Запись пароля
element.innerText = responce.password;
// Запись HTML-элемента с паролем в список
classic.list.appendChild(element);
// Генерация списка с текстом ошибок
_errors(response.errors);
});
}
}
}
}, 500);
/**
* Отправить код приглашения на сервер с дополнительной подготовкой
*
* @return {void}
*/
__invite = () => {
// Блокировка поля ввода
labels.invite.input.disabled = true;
// Реинициализация блока ошибок
_errors();
// Запуск процесса отправки входного псевдонима
_invite();
}
/**
* Сгенерировать HTML-элемент с блоком ошибок
*
* @param {object} registry Реестр ошибок
* @param {bool} reinitialization Реинициализировать?
*
* @return {bool} Сгенерированы ошибки?
*/
_errors = (registry, reinitialization = true) => {
function height() {
// Скрытие HTML-элемента
errors.wrap.style.zIndex = '-99999';
errors.wrap.style.left = '-99999px';
errors.wrap.style.top = '-99999px';
errors.wrap.style.position = 'absolute';
errors.wrap.style.display = 'var(--display, unset)';
errors.wrap.style.opacity = '0';
errors.wrap.style.animationName = 'unset';
// Реинициализация переменной с данными о высоте HTML-элемента
errors.wrap.style.setProperty('--height', errors.wrap.offsetHeight + 'px');
// Отмена скрытия HTML-элемента
errors.wrap.style.zIndex =
errors.wrap.style.left =
errors.wrap.style.top =
errors.wrap.style.position =
errors.wrap.style.display =
errors.wrap.style.opacity =
errors.wrap.style.animationName = null;
}
// Удаление ошибок из прошлой генерации
if (reinitialization) errors.list.innerHTML = null;
for (error in registry) {
// Генерация HTML-элементов с текстами ошибок
// Инициализация HTML-элемента
const element = document.createElement('dd');
if (typeof registry[error] === 'object') {
// Категория ошибок
// Проверка наличия ошибок
if (registry[error].length === 0) continue;
// Инициализация HTML-элемента
const element = document.createElement('dt');
// Запись текста категории
element.innerText = error;
// Запись HTML-элемента в список
errors.list.appendChild(element);
// Реинициализация высоты
height();
// Обработка вложенных ошибок (вход в рекурсию)
_errors(registry[error], false);
} else {
// Текст ошибки (подразумевается)
// Запись текста ошибки
element.innerText = registry[error];
// Запись HTML-элемента в список
errors.list.appendChild(element);
// Реинициализация высоты
height();
}
}
// Реинициализация HTML-элемента с текстом ошибок
if (reinitialization && errors.list.childElementCount === 0) errors.wrap.classList.add('hidden');
else errors.wrap.classList.remove('hidden');
return errors.list.childElementCount === 0 ? false : true;
};
});
</script>
{% endblock %}
{% block js %}
<script type="text/javascript" src="/js/session.js"></script>
<script type="text/javascript" src="/js/password.js"></script>
{% endblock %}

View File

@@ -5,6 +5,10 @@ declare(strict_types=1);
namespace mirzaev\site\account\views;
// Файлы проекта
use mirzaev\site\account\models\session,
mirzaev\site\account\models\account;
// Фреймворк PHP
use mirzaev\minimal\controller;
// Шаблонизатор представлений
@@ -37,13 +41,15 @@ final class templater extends controller implements ArrayAccess
*
* @return void
*/
public function __construct()
public function __construct(?session &$session = null, ?account &$account = null)
{
// Инициализация шаблонизатора
$this->twig = new twig(new FilesystemLoader(VIEWS));
// Инициализация глобальных переменных
$this->twig->addGlobal('cookie', $_COOKIE);
if (isset($session->document)) $this->twig->addGlobal('session', $session);
if (isset($account->document)) $this->twig->addGlobal('account', $account);
}
/**