User Registration Frontend

This commit is contained in:
Roman Hergenreder 2020-07-01 22:13:50 +02:00
parent 23be9fb6d0
commit 86f9e12b87
10 changed files with 198 additions and 166 deletions

@ -34,7 +34,7 @@ namespace Api {
$row = $res[0]; $row = $res[0];
if (strcasecmp($username, $row['name']) === 0) { if (strcasecmp($username, $row['name']) === 0) {
return $this->createError("This username is already taken."); return $this->createError("This username is already taken.");
} else if (strcasecmp($username, $row['email']) === 0) { } else if (strcasecmp($email, $row['email']) === 0) {
return $this->createError("This email address is already in use."); return $this->createError("This email address is already in use.");
} }
} }
@ -670,13 +670,14 @@ namespace Api\User {
} }
parent::__construct($user, $externalCall, $parameters); parent::__construct($user, $externalCall, $parameters);
$this->csrfTokenRequired = false;
} }
private function insertToken() { private function insertToken() {
$validUntil = (new DateTime())->modify("+48 hour"); $validUntil = (new DateTime())->modify("+48 hour");
$sql = $this->user->getSQL(); $sql = $this->user->getSQL();
$res = $sql->insert("UserToken", array("user_id", "token", "token_type", "valid_until")) $res = $sql->insert("UserToken", array("user_id", "token", "token_type", "valid_until"))
->addRow($this->userId, $this->token, "confirmation", $validUntil) ->addRow($this->userId, $this->token, "email_confirm", $validUntil)
->execute(); ->execute();
$this->success = ($res !== FALSE); $this->success = ($res !== FALSE);
@ -732,9 +733,6 @@ namespace Api\User {
$this->userId = $id; $this->userId = $id;
$this->token = generateRandomString(36); $this->token = generateRandomString(36);
if ($this->insertToken()) { if ($this->insertToken()) {
return false;
}
$settings = $this->user->getConfiguration()->getSettings(); $settings = $this->user->getConfiguration()->getSettings();
$baseUrl = htmlspecialchars($settings->getBaseUrl()); $baseUrl = htmlspecialchars($settings->getBaseUrl());
$siteName = htmlspecialchars($settings->getSiteName()); $siteName = htmlspecialchars($settings->getSiteName());
@ -760,6 +758,7 @@ namespace Api\User {
)); ));
$this->lastError = $request->getLastError(); $this->lastError = $request->getLastError();
} }
}
if (!$this->success) { if (!$this->success) {
$this->lastError = "Your account was registered but the confirmation email could not be sent. " . $this->lastError = "Your account was registered but the confirmation email could not be sent. " .

@ -192,7 +192,7 @@ class CreateDatabase {
->addRow("User/edit", array(USER_GROUP_ADMIN), "Allows users to edit details and group memberships of any user") ->addRow("User/edit", array(USER_GROUP_ADMIN), "Allows users to edit details and group memberships of any user")
->addRow("User/delete", array(USER_GROUP_ADMIN), "Allows users to delete any other user") ->addRow("User/delete", array(USER_GROUP_ADMIN), "Allows users to delete any other user")
->addRow("Permission/fetch", array(USER_GROUP_ADMIN), "Allows users to list all API permissions") ->addRow("Permission/fetch", array(USER_GROUP_ADMIN), "Allows users to list all API permissions")
->addRow("Visitors/stats", array(USER_GROUP_ADMIN, USER_GROUP_SUPPORT), "Allowes users to see visitor statistics"); ->addRow("Visitors/stats", array(USER_GROUP_ADMIN, USER_GROUP_SUPPORT), "Allows users to see visitor statistics");
return $queries; return $queries;
} }

@ -17,6 +17,7 @@ namespace Documents {
namespace Documents\Account { namespace Documents\Account {
use Elements\Head; use Elements\Head;
use Elements\Script;
use Elements\SimpleBody; use Elements\SimpleBody;
class AccountHead extends Head { class AccountHead extends Head {
@ -26,11 +27,21 @@ namespace Documents\Account {
} }
protected function initSources() { protected function initSources() {
$this->loadJQuery();
$this->addJS(Script::CORE);
$this->addJS(Script::ACCOUNT);
$this->loadBootstrap();
$this->loadFontawesome();
} }
protected function initMetas() { protected function initMetas() {
return array(); return array(
array('name' => 'viewport', 'content' => 'width=device-width, initial-scale=1.0'),
array('name' => 'format-detection', 'content' => 'telephone=yes'),
array('charset' => 'utf-8'),
array("http-equiv" => 'expires', 'content' => '0'),
array("name" => 'robots', 'content' => 'noarchive'),
);
} }
protected function initRawFields() { protected function initRawFields() {
@ -49,6 +60,7 @@ namespace Documents\Account {
} }
protected function getContent() { protected function getContent() {
$view = $this->getDocument()->getView(); $view = $this->getDocument()->getView();
if ($view === null) { if ($view === null) {
return "The page you does not exist or is no longer valid. <a href='/'>Return to start page</a>"; return "The page you does not exist or is no longer valid. <a href='/'>Return to start page</a>";

@ -11,6 +11,7 @@ class Script extends StaticView {
const JQUERY = "/js/jquery.min.js"; const JQUERY = "/js/jquery.min.js";
const INSTALL = "/js/install.js"; const INSTALL = "/js/install.js";
const BOOTSTRAP = "/js/bootstrap.bundle.min.js"; const BOOTSTRAP = "/js/bootstrap.bundle.min.js";
const ACCOUNT = "/js/account.js";
private string $type; private string $type;
private string $content; private string $content;

@ -0,0 +1,43 @@
<?php
namespace Views\Account;
use Elements\Document;
use Elements\View;
abstract class AccountView extends View {
protected string $description;
public function __construct(Document $document, $loadView = true) {
parent::__construct($document, $loadView);
$this->description = "";
}
public function getCode() {
$html = parent::getCode();
$content = $this->getAccountContent();
$icon = $this->createIcon("user-plus", "fas", "fa-3x");
$html .= "<div class=\"container mt-5\">
<div class=\"row\">
<div class=\"col-md-4 py-5 bg-primary text-white text-center\" style='border-top-left-radius:.4em;border-bottom-left-radius:.4em'>
<div class=\"card-body\">
$icon
<h2 class=\"py-3\">$this->title</h2>
<p>$this->description</p>
</div>
</div>
<div class=\"col-md-8 pt-5 pb-2 border border-info\" style='border-top-right-radius:.4em;border-bottom-right-radius:.4em'>
$content
<div class='alert mt-2' style='display:none' id='alertMessage'></div>
</div>
</div>
</div>";
return $html;
}
protected abstract function getAccountContent();
}

@ -7,15 +7,51 @@ namespace Views\Account;
use Elements\Document; use Elements\Document;
use Elements\View; use Elements\View;
class Register extends View { class Register extends AccountView {
public function __construct(Document $document, $loadView = true) { public function __construct(Document $document, $loadView = true) {
parent::__construct($document, $loadView); parent::__construct($document, $loadView);
$this->title = "Registration";
$this->description = "Create a new account";
} }
public function getCode() { public function loadView() {
$html = parent::getCode(); parent::loadView();
return $html; $document = $this->getDocument();
$settings = $document->getUser()->getConfiguration()->getSettings();
if ($settings->isRecaptchaEnabled()) {
$document->getHead()->loadGoogleRecaptcha($settings->getRecaptchaSiteKey());
}
}
public function getAccountContent() {
$settings = $this->getDocument()->getUser()->getConfiguration()->getSettings();
if (!$settings->isRegistrationAllowed()) {
return $this->createErrorText(
"Registration is not enabled on this website. If you are an administrator,
goto <a href=\"/admin/settings\">/admin/settings</a>, to enable the user registration"
);
}
return "<h4 class=\"pb-4\">Please fill with your details</h4>
<form>
<div class=\"form-group\">
<input id=\"username\" name=\"username\" placeholder=\"Username\" class=\"form-control\" type=\"text\" maxlength=\"32\">
</div>
<div class=\"form-group\">
<input type=\"email\" name='email' id='email' class=\"form-control\" placeholder=\"Email\" maxlength=\"64\">
</div>
<div class=\"form-group\">
<input type=\"password\" name='password' id='password' class=\"form-control\" placeholder=\"Password\">
</div>
<div class=\"form-group\">
<input type=\"password\" name='confirmPassword' id='confirmPassword' class=\"form-control\" placeholder=\"Confirm Password\">
</div>
<div class=\"form-group\">
<button type=\"button\" class=\"btn btn-success\" id='btnRegister'>Submit</button>
</div>
</form>";
} }
} }

@ -0,0 +1,21 @@
<?php
namespace Views\Account;
use Elements\Document;
use Elements\View;
class ResetPassword extends View {
public function __construct(Document $document, $loadView = true) {
parent::__construct($document, $loadView);
}
public function getCode() {
$html = parent::getCode();
return $html;
}
}

46
js/account.js Normal file

@ -0,0 +1,46 @@
$(document).ready(function () {
function showAlert(type, msg) {
let alert = $("#alertMessage");
alert.text(msg);
alert.attr("class", "mt-2 alert alert-" + type);
alert.show();
}
function hideAlert() {
$("#alertMessage").hide();
}
$("#btnRegister").click(function (e) {
e.preventDefault();
e.stopPropagation();
let btn = $(this);
let username = $("#username").val().trim();
let email = $("#email").val().trim();
let password = $("#password").val();
let confirmPassword = $("#confirmPassword").val();
if (username === '' || email === '' || password === '' || confirmPassword === '') {
showAlert("danger", "Please fill out every field.");
} else if(password !== confirmPassword) {
showAlert("danger", "Your passwords did not match.");
} else {
let textBefore = btn.text();
let params = { username: username, email: email, password: password, confirmPassword: confirmPassword };
btn.prop("disabled", true);
btn.html("Submitting… <i class='fas fa-spin fa-spinner'></i>")
jsCore.apiCall("user/register", params, (res) => {
btn.prop("disabled", false);
btn.text(textBefore);
if (!res.success) {
showAlert("danger", res.msg);
} else {
showAlert("success", "Account successfully created, check your emails.");
$("input").val("");
}
});
}
});
});

4
js/jquery.min.js vendored

File diff suppressed because one or more lines are too long

@ -3,32 +3,20 @@ let Core = function () {
this.__construct = function () { this.__construct = function () {
this.url = document.location.href; this.url = document.location.href;
this.parseParameters(); this.parseParameters();
this.langEntries = {};
}; };
this.apiCall = function (func, aParams, callback, onerror) { this.apiCall = function (func, params, callback) {
aParams = typeof aParams !== 'undefined' ? aParams : {}; params = typeof params !== 'undefined' ? params : {};
callback = typeof callback !== 'undefined' ? callback : function (data) {}; callback = typeof callback !== 'undefined' ? callback : function (data) {};
onerror = typeof onerror !== 'undefined' ? onerror : function (msg) { const path = '/api/' + (func.startsWith('/') ? '' : '/') + func;
bootbox.alert("An error occurred: " + msg); $.post(path, params, function (data) {
};
const path = '/api' + (func.startsWith('/') ? '' : '/') + func;
$.post(path, aParams, function (data) {
console.log(func + "(): success=" + data.success + " msg=" + data.msg); console.log(func + "(): success=" + data.success + " msg=" + data.msg);
if (data.hasOwnProperty('logoutIn') && $("#logoutTimer").length > 0) {
$("#logoutTimer").attr("data-time", data.logoutIn);
}
if (!data.success) {
onerror.call(this, data.msg);
} else {
callback.call(this, data); callback.call(this, data);
}
}, "json").fail(function (jqXHR, textStatus, errorThrown) { }, "json").fail(function (jqXHR, textStatus, errorThrown) {
console.log("API-Function Error: " + func + " Status: " + textStatus + " error thrown: " + errorThrown); let msg = func + " Status: " + textStatus + " error thrown: " + errorThrown;
onerror.call(this, "An error occurred. API-Function: " + func + " Status: " + textStatus + " - " + errorThrown); console.log("API-Function Error: " + msg);
callback.call(this, {success: false, msg: "An error occurred. API-Function: " + msg });
}); });
}; };
@ -48,18 +36,6 @@ let Core = function () {
return ""; return "";
}; };
this.addLangEntry = function (key, val) {
this.langEntries[key] = val;
};
this.getLangEntry = function (key) {
if (typeof this.langEntries[key] !== 'undefined' && this.langEntries.hasOwnProperty(key)) {
return this.langEntries[key];
}
return key;
};
this.getUrl = function () { this.getUrl = function () {
return this.url; return this.url;
}; };
@ -155,10 +131,6 @@ let Core = function () {
this.url = this.url.substring(0, this.url.indexOf('?')); this.url = this.url.substring(0, this.url.indexOf('?'));
}; };
this.logout = function () {
this.apiCall('user/logout');
};
this.getJsonDateTime = function (date) { this.getJsonDateTime = function (date) {
return date.getFullYear() + "-" + return date.getFullYear() + "-" +
((date.getMonth() + 1 < 10) ? "0" : "") + (date.getMonth() + 1) + "-" + ((date.getMonth() + 1 < 10) ? "0" : "") + (date.getMonth() + 1) + "-" +
@ -176,105 +148,7 @@ let Core = function () {
return this.getJsonDateTime(date).split(' ')[1]; return this.getJsonDateTime(date).split(' ')[1];
}; };
this.showInputDialog = function (title, aInputs, callback, element, onCreated) {
title = typeof title !== "undefined" ? title : "";
aInputs = typeof aInputs !== "undefined" ? aInputs : {};
callback = typeof callback !== "undefined" ? callback : function (aResult, element) {
};
onCreated = typeof onCreated !== "undefined" ? onCreated : function () {
};
var html = '<div class="modal-header"><h4 class="modal-title">' + title + '</h4></div>' +
'<form class="bootbox-form">';
for (var i in aInputs) {
var input = aInputs[i];
if (input.type !== "hidden" && input.type !== "checkbox")
html += '<label for="' + input.name + '">' + input.name + ':</label>';
if (input.type === "select") {
html += '<select id="' + input.id + '" class="bootbox-input bootbox-input-select form-control">';
var aValues = (input.hasOwnProperty("aValues") && typeof input.aValues !== "undefined") ? input.aValues : {};
for (var value in aValues) {
var name = aValues[value];
var selected = (input.value === value) ? " selected" : "";
html += '<option value="' + value + '"' + selected + '>' + name + '</option>';
}
html += '</select>';
input.type = "select";
} else if (input.type === "checkbox") {
html += '<div class="checkbox">' +
'<label><table><tr>' +
'<td style="vertical-align:top;padding-top:3px;">' +
'<input class="bootbox-input bootbox-input-checkbox" id="' + input.id + '" value="1" type="checkbox"' + (input.value ? " checked" : "") + '>' +
'</td>' +
'<td style="padding-left: 5px;">' + input.text + '</td>' +
'</tr></table></label>' +
'</div>';
} else if (input.type === "date") {
html += '<input class="bootbox-input form-control customDatePicker" autocomplete="off" ' +
'type="text" ' +
'name="' + input.name + '" ' +
'id="' + input.id + '" ' +
'value="' + (input.value ? input.value : "") + '"' + (input.readonly ? " readonly" : "") +
(input.maxlength ? ' maxlength="' + input.maxlength + '"' : '') + '>';
} else if (input.type === "time") {
html += '<div class="input-group">' +
'<input class="bootbox-input" autocomplete="off" value="0" pattern="[0-9][0-9]" type="number" name="' + input.name + '" id="' + input.id + 'Hour" style="width:60px;text-align: center">' +
'<span>:</span>' +
'<input class="bootbox-input" autocomplete="off" type="number" name="' + input.name + '" id="' + input.id + 'Minute" value="00" style="width:60px;text-align: center">' +
'</div>';
} else {
html += '<input class="bootbox-input form-control" autocomplete="off" ' +
'type="' + input.type + '" ' +
'name="' + input.name + '" ' +
'id="' + input.id + '" ' +
'value="' + (input.value ? input.value : "") + '"' + (input.readonly ? " readonly" : "") +
(input.maxlength ? ' maxlength="' + input.maxlength + '"' : '') + '>';
}
}
html += '</form>';
var dialog = bootbox.confirm(html, function (result) {
if (result) {
var aResult = [];
for (var i in aInputs) {
var input = aInputs[i];
var value = $("#" + input.id).val();
if (input.type === "select")
value = $("#" + input.id).find(":selected").val();
else if (input.type === "checkbox")
value = $("#" + input.id).prop("checked");
aResult[input.id] = value;
}
callback.call(this, aResult, element);
}
});
dialog.init(function () {
$(".modal-body").on("keypress", "input", function (e) {
if (e.keyCode === 13) {
e.preventDefault();
$(".modal-footer .btn-primary").click();
}
});
onCreated.call(this);
});
};
this.__construct(); this.__construct();
}; };
let jsCore = new Core(); let jsCore = new Core();
$(document).ready(function() {
});
function createLoadingIcon() {
return '<i class="fas fa-spin fa-spinner"></i>';
}