Сегодня мы с Вами напишем с нуля полноценный скрипт регистрации и авторизации для сервера rage mp. В качестве интерфейса мы не будем использовать команды, а сразу сделаем "красиво" на CEF. В качестве базы данных будем использовать MySQL.
Видео версия как обычно на youtube канале:
Для начала я нашел в Интернете простенький HTML шаблон страницы авторизации: https://codepen.io/colorlib/pen/rxddKy
Помещаем его в папку cef нашего клиентского скрипта accounts. Туда же ложим стили (style.css) и браузерные скрипты (script.js), которые мы напишем дальше.
Я немного модифицировал шаблон:
1. Добавил фоновую картинку
2. Расставил id для полей ввода, чтобы было удобнее работать с ними.
3. Убрал неиспользуемые стили
4. Добавил блок для вывода ошибок
5. Перевел на русский язык.
В итоге html файл выглядит так:
Чтобы показать этот интерфейс игроку при входе на сервер, будем дергать с сервера на клиент событие showLoginDialog.
На клиентской стороне добавим обработчик этого события
Теперь при входе игрока на сервер ему будет показываться на форма входа.
Вернемся теперь к index.html и браузерной части. У нас есть две формы (register и login). Форма login отображается по-умолчанию, а register скрыта в стилях. Внизу каждой формы есть ссылка на другую и при помощи функций
В script.js добавим реализацию этих функций. С переключением между формами все просто:
При отправке запроса на вход или регистрацию нам нужно считать содержимое полей формы, выполнить их базовые проверки и передать в клиент rage mp
mp.trigger позволяем нам отправить из браузера на клиент только один дополнительный параметр. И это может быть только строка или число. Нам же нужно отправить два значения. Мы не можем отправить напрямую массив или объект, но мы можем преобразовать наш объект с логином и паролем в json строку
Также в коде Вы наверное заметили функции связанные с выводом ошибок в форму на нашей страничке. Здесь все просто. У нас есть div блок с id error. Он находится выше наших форм и поэтому может показываться независимо от того на какой форме сейчас пользователь.
В клиентской части добавим обработчики событий loginAttempt и registerAttempt, которые будут вызываться из браузерного скрипта.
Они максимально простые и просто передают данные с браузера дальше на сервер при помощи callRemote. Напоминаю что в качестве data у нас JSON строка с логином и паролем. В таком виде мы передаем ее дальше, поскольку callRemote также позволяет нам передавать только простые строки и числа.
На серверной стороне прежде чем обработать события onLoginAttempt и onRegisterAttempt нужно кое-что подготовить:
На клиенте событие showAuthError просто показывает текст ошибки в форме.
А при успешном входе мы скрываем окно авторизации и считаем что игрок авторизовался
Для регистрации на серверной стороне обработчик onRegisterAttempt будет немного сложнее. Прежде чем добавить аккаунт нам нужно проверить его на уникальность и его такой логин уже есть в базе данных, то выдавать ошибку.
Для тех кто захочет дальше ковырять эту форму, напишу парочку идей того, что можно улучшить и доработать:
1. Добавить защиту от перебора паролей. Кикать после 3 неправильных вводов.
2. Написать функцию isPlayerLoggedIn() которая будет возвращать true если игрок авторизовался и false если еще нет.
3. Добавить столбец position в таблицу accounts. Записывать туда позицию игрока при выходе с сервера.
4. Добавить возможность восстановить пароль. Для этого понадобиться добавить поле для email аккаунта и какой-то способ чтобы отправлять электронные письма с сервера.
Решение задач от пользователя @geneff
Задачи 1-3
Задача 4 (восстановление пароля)
Если при установке библиотеки bcrypt появляется ошибка 'undefined symbol: napi_module_register', то попробуйте это решение.
Видео версия как обычно на youtube канале:
Для начала я нашел в Интернете простенький HTML шаблон страницы авторизации: https://codepen.io/colorlib/pen/rxddKy
Помещаем его в папку cef нашего клиентского скрипта accounts. Туда же ложим стили (style.css) и браузерные скрипты (script.js), которые мы напишем дальше.
Я немного модифицировал шаблон:
1. Добавил фоновую картинку
2. Расставил id для полей ввода, чтобы было удобнее работать с ними.
3. Убрал неиспользуемые стили
4. Добавил блок для вывода ошибок
5. Перевел на русский язык.
В итоге html файл выглядит так:
HTML:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="package://accounts/cef/style.css">
</head>
<body>
<div class="login-page">
<div class="form">
<div id="error"></div>
<form class="register-form" id="register">
<input id="reg-login" type="text" placeholder="Логин"/>
<input id="reg-password" type="password" placeholder="Пароль"/>
<input id="reg-password-confirm" type="password" placeholder="Повторите Пароль"/>
<button type="button" onclick="registerAttempt()">Регистрация</button>
<p class="message">Уже зарегистрированы? <a href="#" onclick="showLogin()">Войти</a></p>
</form>
<form class="login-form" id="login">
<input id="log-login" type="text" placeholder="Логин"/>
<input id="log-password" type="password" placeholder="Пароль"/>
<button type="button" onclick="loginAttempt()">Войти</button>
<p class="message">Не зарегистрированы? <a href="#" onclick="showRegister()">создать аккаунт</a></p>
</form>
</div>
</div>
<script src="package://accounts/cef/script.js"></script>
</body>
</html>
Чтобы показать этот интерфейс игроку при входе на сервер, будем дергать с сервера на клиент событие showLoginDialog.
JavaScript:
mp.events.add("playerReady", player => {
player.call('showLoginDialog');
});
На клиентской стороне добавим обработчик этого события
Код:
let loginBrowser;
mp.events.add('showLoginDialog', () => {
loginBrowser = mp.browsers.new('package://accounts/cef/index.html'); // инициализируем браузер и отображаем страничку входа
loginBrowser.execute("mp.invoke('focus', true)"); // показываем курсор
mp.gui.chat.activate(false); // блокируем открытие чата при вводе текста в поля формы
});
Теперь при входе игрока на сервер ему будет показываться на форма входа.
Вернемся теперь к index.html и браузерной части. У нас есть две формы (register и login). Форма login отображается по-умолчанию, а register скрыта в стилях. Внизу каждой формы есть ссылка на другую и при помощи функций
showLogin()
и showRegister()
мы будем переключаться между ними. Также для кнопок входа и регистрации добавлен вызов функций registerAttempt()
и loginAttempt()
, которые будут вызываться по событию onclickВ script.js добавим реализацию этих функций. С переключением между формами все просто:
JavaScript:
function showRegister(){
document.getElementById('login').style.display = 'none';
document.getElementById('register').style.display = 'block';
}
function showLogin(){
document.getElementById('login').style.display = 'block';
document.getElementById('register').style.display = 'none';
}
При отправке запроса на вход или регистрацию нам нужно считать содержимое полей формы, выполнить их базовые проверки и передать в клиент rage mp
JavaScript:
function registerAttempt(){
// считываем содержимое полей
const login = document.getElementById('reg-login').value;
const password = document.getElementById('reg-password').value;
const passwordConfirm = document.getElementById('reg-password-confirm').value;
resetError();
// Проверяем чтобы поля были заполнены, они были нужной длинны и пароли совпадали
if(!login || login.length < 3){
return showError('Введите логин');
}
if(!password || password.length < 6){
return showError('Введите пароль');
}
if(password != passwordConfirm){
return showError('Пароли не совпадают');
}
// Отправляем логин и пароль на клиент
mp.trigger('registerAttempt', JSON.stringify({ login, password }) );
}
function loginAttempt(){
const login = document.getElementById('log-login').value;
const password = document.getElementById('log-password').value;
resetError();
if(!login || login.length < 3){
return showError('Введите логин');
}
if(!password || password.length < 6){
return showError('Введите пароль');
}
mp.trigger('loginAttempt', JSON.stringify({ login, password }) );
}
mp.trigger позволяем нам отправить из браузера на клиент только один дополнительный параметр. И это может быть только строка или число. Нам же нужно отправить два значения. Мы не можем отправить напрямую массив или объект, но мы можем преобразовать наш объект с логином и паролем в json строку
JSON.stringify({ login, password })
. И теперь эту строку мы легко передаем в одном аргументе.Также в коде Вы наверное заметили функции связанные с выводом ошибок в форму на нашей страничке. Здесь все просто. У нас есть div блок с id error. Он находится выше наших форм и поэтому может показываться независимо от того на какой форме сейчас пользователь.
JavaScript:
function showError(message){
const errorBlock = document.getElementById('error');
errorBlock.innerText = message;
errorBlock.style.display = 'block';
}
function resetError(){
const errorBlock = document.getElementById('error');
errorBlock.innerText = '';
errorBlock.style.display = 'none';
}
В клиентской части добавим обработчики событий loginAttempt и registerAttempt, которые будут вызываться из браузерного скрипта.
JavaScript:
mp.events.add('loginAttempt', (data) => {
mp.events.callRemote('onLoginAttempt', data);
});
mp.events.add('registerAttempt', (data) => {
mp.events.callRemote('onRegisterAttempt', data);
});
Они максимально простые и просто передают данные с браузера дальше на сервер при помощи callRemote. Напоминаю что в качестве data у нас JSON строка с логином и паролем. В таком виде мы передаем ее дальше, поскольку callRemote также позволяет нам передавать только простые строки и числа.
На серверной стороне прежде чем обработать события onLoginAttempt и onRegisterAttempt нужно кое-что подготовить:
- Добавить пакет mysql и настроить подключение к серверу MySQL. Структура базы данных и само подключение будет таким же, как и в уроке по подключению MySQL. У нас будет 1 таблица accounts с тремя столбцами: id, login и password
- Добавить пакет bcrypt для генерации хэша паролей и его проверки.
JavaScript:
mp.events.add('onLoginAttempt', (player, data) => {
data = JSON.parse(data); // преобразовуем данные из json в объект
DB.query('SELECT * FROM accounts WHERE login = ? LIMIT 1', [data.login], function (error, results) { // ищем аккаунт по логину
if(results.length == 0) return player.call('showAuthError', ['Неверный Логин и/или Пароль']); // если аккаунт с таким логином не найден, то возвращаем на клиент текст ошибки
const passwordHash = results[0].password; // если же аккаунт есть, то берем его хеш пароля
bcrypt.compare(data.password, passwordHash, function(err, isMatched) { // сравниваем хэши паролей из базы данных и того что указал пользователь
if( isMatched ) return player.call('hideLoginDialog'); // если пароли не совпадают, значит пользователь авторизовался успешно
player.call('showAuthError', ['Неверный Логин и/или Пароль']); // если же пароли не совпали, то опять таки возвращаем на клиент текст ошибки при помощи события showAuthError
});
});
});
На клиенте событие showAuthError просто показывает текст ошибки в форме.
JavaScript:
mp.events.add('showAuthError', (errorMessage) => {
loginBrowser.execute(`showError("${errorMessage}")`);
});
А при успешном входе мы скрываем окно авторизации и считаем что игрок авторизовался
JavaScript:
mp.events.add('hideLoginDialog', () => {
loginBrowser.execute("mp.invoke('focus', false)");
loginBrowser.active = false;
mp.gui.chat.activate(true);
});
Для регистрации на серверной стороне обработчик onRegisterAttempt будет немного сложнее. Прежде чем добавить аккаунт нам нужно проверить его на уникальность и его такой логин уже есть в базе данных, то выдавать ошибку.
JavaScript:
mp.events.add('onRegisterAttempt', (player, data) => {
data = JSON.parse(data);
DB.query('SELECT id FROM accounts WHERE login = ?', [data.login], function (error, results) { // Проверяем уникальность логина
if(results.length > 0) return player.call('showAuthError', ['Аккаунт с таким Логином уже существует']); // Если такой логин уже есть, то возвращаем ошибку
bcrypt.hash(data.password, saltRounds, function(err, passwordHash) { // Создаем хэш пароля
DB.query('INSERT INTO accounts SET login = ?, password = ?', [data.login, passwordHash], function (error, results) { // Добавляем аккаунт в базу данных
player.call('hideLoginDialog'); // Скрываем окно авторизации
});
});
});
});
Для тех кто захочет дальше ковырять эту форму, напишу парочку идей того, что можно улучшить и доработать:
1. Добавить защиту от перебора паролей. Кикать после 3 неправильных вводов.
2. Написать функцию isPlayerLoggedIn() которая будет возвращать true если игрок авторизовался и false если еще нет.
3. Добавить столбец position в таблицу accounts. Записывать туда позицию игрока при выходе с сервера.
4. Добавить возможность восстановить пароль. Для этого понадобиться добавить поле для email аккаунта и какой-то способ чтобы отправлять электронные письма с сервера.
Решение задач от пользователя @geneff
Задачи 1-3
Задача 4 (восстановление пароля)
Если при установке библиотеки bcrypt появляется ошибка 'undefined symbol: napi_module_register', то попробуйте это решение.
Вложения
Последнее редактирование: