Split auth.php up into multiple files.

This commit is contained in:
flash 2019-03-08 01:35:53 +01:00
parent 4d47d290bf
commit 9c739e1062
31 changed files with 811 additions and 692 deletions

View file

@ -1,49 +0,0 @@
.auth {
max-width: 400px;
margin: 0 auto;
margin-bottom: 2px;
&__header {
display: flex;
justify-content: center;
padding: 20px;
}
&__warning {
margin: 2px 5px 0;
&--welcome {
--start-colour: var(--accent-colour);
--end-colour: #222;
}
}
&__avatar {
width: 100px;
height: 100px;
}
&__paragraph,
&__input {
margin-bottom: 5px;
}
&__form {
padding: 5px;
display: flex;
flex-direction: column;
}
&__buttons {
display: flex;
justify-content: space-evenly;
}
&__button {
flex: 1 1 auto;
&:not(:first-child) {
margin-left: 2px;
}
}
}

View file

@ -0,0 +1,14 @@
.auth__buttons {
margin: 5px;
display: flex;
flex-direction: row-reverse;
justify-content: space-between;
&__button {
&--minor {
background-color: transparent;
border-color: transparent;
color: inherit;
}
}
}

View file

@ -0,0 +1,4 @@
.auth__container {
margin: 2px auto;
max-width: 400px;
}

View file

@ -0,0 +1,39 @@
.auth__label {
overflow: hidden;
margin-bottom: 5px;
display: block;
&__text {
padding: 5px 10px;
}
&__value {
padding: 2px 5px;
}
&__input {
width: 100%;
}
&__action {
padding: 3px 8px;
display: block;
color: inherit;
text-decoration: none;
position: absolute;
top: 0;
right: 0;
border-radius: 4px;
transition: background-color .2s;
margin: 2px;
&:hover,
&:focus {
background-color: fade(#fff, 20%);
}
&:active {
background-color: fade(#fff, 10%);
}
}
}

View file

@ -0,0 +1,19 @@
.auth__login {
&--disabled {
--accent-colour: #555;
opacity: .5;
}
&__header {
display: flex;
justify-content: center;
padding: 20px;
}
&__avatar {
width: 100px;
height: 100px;
margin: 10px;
}
}

View file

@ -0,0 +1,7 @@
.auth__logout {
margin: 5px;
&__paragraph {
margin: 5px 0;
}
}

View file

@ -0,0 +1,37 @@
.auth__register {
max-width: 700px;
width: 100%;
&__container {
display: flex;
@media (max-width: @site-mobile-width) {
flex-direction: column;
}
}
&__form {
flex: 1 1 auto;
}
&__info {
max-width: 300px;
width: 100%;
flex: 0 1 auto;
padding: 5px;
@media (max-width: @site-mobile-width) {
max-width: 100%;
}
}
&__paragraph {
line-height: 1.5em;
margin: 5px 0;
}
&__link {
color: inherit;
text-decoration: underline;
}
}

View file

@ -0,0 +1,12 @@
.auth__warning {
margin: 5px;
&--welcome {
--start-colour: var(--accent-colour);
--end-colour: #222;
}
&__paragraph {
line-height: 2em;
}
}

View file

@ -7,7 +7,7 @@
min-width: 80px;
text-align: center;
cursor: pointer;
transition: color .2s, background-color .2s, opacity .2s;
transition: color .2s, background-color .2s, opacity .2s, border-color .2s;
color: var(--accent-colour);
border: 1px solid var(--accent-colour);
border-radius: 2px;
@ -21,6 +21,7 @@
&:focus {
color: #111;
background-color: var(--accent-colour);
border-color: var(--accent-colour);
}
&--busy {

View file

@ -121,7 +121,6 @@ html {
@import "classes/messagebox";
// Specific styles
@import "classes/auth";
@import "classes/header";
@import "classes/footer";
@import "classes/permissions";
@ -130,6 +129,15 @@ html {
@import "classes/home";
@import "classes/landing";
// Auth
@import "classes/auth/buttons";
@import "classes/auth/container";
@import "classes/auth/label";
@import "classes/auth/login";
@import "classes/auth/logout";
@import "classes/auth/register";
@import "classes/auth/warning";
// Manage
@import "classes/manage/manage";
@import "classes/manage/navigation";

View file

@ -32,15 +32,6 @@ window.addEventListener('load', () => {
changelogChangeAction.title = "This is supposed to be sideways, but your browser doesn't support that.";
}
const loginButtons: HTMLCollectionOf<HTMLAnchorElement> = document.getElementsByClassName('js-login-button') as HTMLCollectionOf<HTMLAnchorElement>;
if (loginButtons.length > 0) {
for (let i = 0; i < loginButtons.length; i++) {
loginButtons[i].href = 'javascript:void(0);';
loginButtons[i].addEventListener('click', () => loginModal());
}
}
const loginForms: HTMLCollectionOf<HTMLFormElement> = document.getElementsByClassName('js-login-form') as HTMLCollectionOf<HTMLFormElement>;
if (loginForms.length > 0) {
@ -80,7 +71,7 @@ function loginFormUpdateAvatar(avatarElement: HTMLElement, usernameElement: HTML
avatarElement.style.backgroundImage = "url('{0}')".replace('{0}', urlFormat('user-avatar', [{name: 'user', value: xhr.responseText}]));
});
xhr.open('GET', urlFormat('auth-resolve-user', [{name: 'username', value: encodeURI(usernameElement.value)}]));
xhr.open('GET', urlFormat('auth-resolve-user', [{name: 'username', value: encodeURIComponent(usernameElement.value)}]));
xhr.send();
}
@ -142,77 +133,3 @@ function messageBox(text: string, title: string = null, buttons: MessageBoxButto
firstButton.focus();
return true;
}
function loginModal(): boolean {
if (document.querySelector('.messagebox') || getCurrentUser('user_id') > 0) {
return false;
}
const element: HTMLDivElement = document.createElement('div');
element.className = 'messagebox';
const container: HTMLFormElement = element.appendChild(document.createElement('form'));
container.className = 'container messagebox__container auth js-login-form';
container.method = 'post';
container.action = urlFormat('auth-login');
const titleElement = container.appendChild(document.createElement('div')),
titleBackground = titleElement.appendChild(document.createElement('div')),
titleHeader = titleElement.appendChild(document.createElement('div'));
titleElement.className = 'container__title';
titleBackground.className = 'container__title__background';
titleHeader.className = 'auth__header';
const authAvatar: HTMLDivElement = titleHeader.appendChild(document.createElement('div'));
authAvatar.className = 'avatar auth__avatar';
authAvatar.style.backgroundImage = "url('{0}')".replace('{0}', urlFormat('user-avatar'));
const hiddenMode: HTMLInputElement = container.appendChild(document.createElement('input'));
hiddenMode.type = 'hidden';
hiddenMode.name = 'auth[mode]';
hiddenMode.value = 'login';
const hiddenCsrf: HTMLInputElement = container.appendChild(document.createElement('input'));
hiddenCsrf.type = 'hidden';
hiddenCsrf.name = 'csrf[login]';
hiddenCsrf.value = getCSRFToken('login');
const hiddenRedirect: HTMLInputElement = container.appendChild(document.createElement('input'));
hiddenRedirect.type = 'hidden';
hiddenRedirect.name = 'auth[redirect]';
hiddenRedirect.value = location.toString();
const authForm: HTMLDivElement = container.appendChild(document.createElement('div'));
authForm.className = 'auth__form';
const inputUsername: HTMLInputElement = authForm.appendChild(document.createElement('input'));
inputUsername.className = 'input__text auth__input';
inputUsername.placeholder = 'Username';
inputUsername.type = 'text';
inputUsername.name = 'auth[username]';
inputUsername.addEventListener('keyup', () => loginFormUpdateAvatar(authAvatar, inputUsername));
const inputPassword: HTMLInputElement = authForm.appendChild(document.createElement('input'));
inputPassword.className = 'input__text auth__input';
inputPassword.placeholder = 'Password';
inputPassword.type = 'password';
inputPassword.name = 'auth[password]';
const formButtons: HTMLDivElement = authForm.appendChild(document.createElement('div'));
formButtons.className = 'auth__buttons';
const inputLogin: HTMLButtonElement = formButtons.appendChild(document.createElement('button'));
inputLogin.className = 'input__button auth__button';
inputLogin.textContent = 'Log in';
const inputClose: HTMLButtonElement = formButtons.appendChild(document.createElement('button'));
inputClose.className = 'input__button auth__button';
inputClose.textContent = 'Close';
inputClose.type = 'button';
inputClose.addEventListener('click', () => element.remove());
document.body.appendChild(element);
inputUsername.focus();
return true;
}

View file

@ -386,16 +386,6 @@ MIG;
tpl_add_path(MSZ_ROOT . '/templates');
$misuzuBypassLockdown = !empty($misuzuBypassLockdown);
if (!$misuzuBypassLockdown && boolval(config_get_default(false, 'Auth', 'lockdown'))) {
http_response_code(503);
echo tpl_render('auth.lockdown', [
'message' => config_get_default(null, 'Auth', 'lockdown_msg'),
]);
exit;
}
if (file_exists(MSZ_ROOT . '/.migrating')) {
http_response_code(503);
echo tpl_render('auth.lockdown', [
@ -448,32 +438,32 @@ MIG;
empty($userDisplayInfo) ? ip_remote_address() : $cookieData['session_token']
);
if (!$misuzuBypassLockdown && boolval(config_get_default(false, 'Private', 'enabled'))) {
if (user_session_active()) {
$privatePermission = intval(config_get_default(0, 'Private', 'permission'));
if (config_get_default(false, 'Private', 'enabled')) {
$onLoginPage = $_SERVER['PHP_SELF'] === url('auth-login');
$onPasswordPage = parse_url($_SERVER['PHP_SELF'], PHP_URL_PATH) === url('auth-forgot');
$misuzuBypassLockdown = !empty($misuzuBypassLockdown) || $onLoginPage;
if ($privatePermission > 0) {
$generalPerms = perms_get_user(MSZ_PERMS_GENERAL, $userDisplayInfo['user_id']);
if (!$misuzuBypassLockdown) {
if (user_session_active()) {
$privatePermission = (int)config_get_default(0, 'Private', 'permission');
if (!perms_check($generalPerms, $privatePermission)) {
unset($userDisplayInfo);
user_session_stop(); // au revoir
if ($privatePermission > 0) {
$generalPerms = perms_get_user(MSZ_PERMS_GENERAL, $userDisplayInfo['user_id']);
if (!perms_check($generalPerms, $privatePermission)) {
unset($userDisplayInfo);
user_session_stop(); // au revoir
}
}
} elseif (!$onLoginPage && !($onPasswordPage && config_get_default(false, 'Private', 'password_reset'))) {
header(sprintf('Location: %s', url('auth-login')));
exit;
}
} else {
http_response_code(401);
echo tpl_render('auth.private', [
'private_message'=> config_get_default('', 'Private', 'message'),
]);
exit;
}
}
if (!empty($userDisplayInfo)) {
tpl_var('current_user', $userDisplayInfo);
} else {
// make sure the login csrf token is available
csrf_token('login');
}
$inManageMode = starts_with($_SERVER['REQUEST_URI'], '/manage');

View file

@ -1,358 +1,27 @@
<?php
$isSubmission = !empty($_POST['auth']) && is_array($_POST['auth']);
$authMode = $isSubmission ? ($_POST['auth']['mode'] ?? '') : ($_GET['m'] ?? 'login');
$misuzuBypassLockdown = $authMode === 'login' || $authMode === 'get_user';
// Delete this file in April 2019
require_once '../misuzu.php';
$siteIsPrivate = boolval(config_get_default(false, 'Private', 'enabled'));
$loginPermission = $siteIsPrivate ? intval(config_get_default(0, 'Private', 'permission')) : 0;
$canResetPassword = $siteIsPrivate ? boolval(config_get_default(false, 'Private', 'password_reset')) : true;
$canCreateAccount = !$siteIsPrivate && !boolval(config_get_default(false, 'Auth', 'lockdown'));
$authUsername = $isSubmission ? ($_POST['auth']['username'] ?? '') : ($_GET['username'] ?? '');
$authEmail = $isSubmission ? ($_POST['auth']['email'] ?? '') : ($_GET['email'] ?? '');
$authPassword = $_POST['auth']['password'] ?? '';
$authVerification = $_POST['auth']['verification'] ?? '';
$authRedirect = $_POST['auth']['redirect'] ?? $_GET['redirect'] ?? $_SERVER['HTTP_REFERER'] ?? '/';
$authRestricted = ip_blacklist_check(ip_remote_address())
? 1
: (
user_warning_check_ip(ip_remote_address())
? 2
: 0
);
tpl_vars([
'can_create_account' => $canCreateAccount,
'can_reset_password' => $canResetPassword,
'auth_mode' => $authMode,
'auth_username' => $authUsername,
'auth_email' => $authEmail,
'auth_redirect' => $authRedirect,
'auth_restricted' => $authRestricted,
]);
switch ($authMode) {
case 'get_user':
echo user_id_from_username($_GET['u'] ?? '');
break;
switch ($_GET['m'] ?? '') {
case 'logout':
if (!user_session_active()) {
header(sprintf('Location: %s', url('index')));
return;
}
if (csrf_verify('logout', $_GET['s'] ?? '')) {
setcookie('msz_auth', '', -3600, '/', '', true, true);
user_session_stop(true);
header(sprintf('Location: %s', url('index')));
return;
}
echo tpl_render('auth.logout');
break;
case 'reset':
// If we're logged in, redirect to the password/e-mail change part in settings instead.
if (user_session_active()) {
header(sprintf('Location: %s', url('settings-mode', ['mode' => 'account'])));
break;
}
if (!$canResetPassword) {
header(sprintf('Location: %s', url('index')));
return;
}
$resetUserId = (int)($_POST['user'] ?? $_GET['u'] ?? 0);
if (empty($resetUserId)) {
header(sprintf('Location: %s', url('auth-forgot')));
break;
}
$resetUsername = user_username_from_id($resetUserId);
if (empty($resetUsername)) {
header(sprintf('Location: %s', url('auth-login')));
break;
}
tpl_var('auth_reset_message', "A verification code should've been sent to your e-mail address.");
while ($isSubmission) {
if (!csrf_verify('passreset', $_POST['csrf'] ?? '')) {
tpl_var('auth_reset_error', 'Possible request forgery detected, refresh and try again.');
break;
}
if (!user_recovery_token_validate($resetUserId, $authVerification)) {
tpl_var('auth_reset_error', 'Invalid verification code!');
break;
}
tpl_var('reset_verify', $authVerification);
if (empty($authPassword['new'])
|| empty($authPassword['confirm'])
|| $authPassword['new'] !== $authPassword['confirm']) {
tpl_var('auth_reset_error', 'Your passwords didn\'t match!');
break;
}
if (user_validate_password($authPassword['new']) !== '') {
tpl_var('auth_reset_error', 'Your password is too weak!');
break;
}
if (user_password_set($resetUserId, $authPassword['new'])) {
audit_log(MSZ_AUDIT_PASSWORD_RESET, $resetUserId);
} else {
throw new UnexpectedValueException('Password reset failed.');
}
user_recovery_token_invalidate($resetUserId, $authVerification);
header(sprintf('Location: %s', url('auth-login')));
break;
}
echo tpl_render('auth.password', [
'reset_user' => [
'user_id' => $resetUserId,
'username' => $resetUsername,
],
]);
header('Location: ' . url('auth-reset'));
break;
case 'forgot':
if (user_session_active() || !$canResetPassword) {
header(sprintf('Location: %s', url('index')));
break;
}
while ($isSubmission) {
if (!csrf_verify('passforgot', $_POST['csrf'] ?? '')) {
tpl_var('auth_forgot_error', 'Possible request forgery detected, refresh and try again.');
break;
}
if (empty($authEmail)) {
tpl_var('auth_forgot_error', 'Please enter an e-mail address.');
break;
}
$forgotUser = user_find_for_reset($authEmail);
if (empty($forgotUser)) {
tpl_var('auth_forgot_error', 'This user is not registered with us.');
break;
}
$ipAddress = ip_remote_address();
if (!user_recovery_token_sent($forgotUser['user_id'], $ipAddress)) {
$verificationCode = user_recovery_token_create($forgotUser['user_id'], $ipAddress);
if (empty($verificationCode)) {
throw new UnexpectedValueException('A verification code failed to insert.');
}
$messageBody = <<<MSG
Hey {$forgotUser['username']},
You, or someone pretending to be you, has requested a password reset for your account.
Your verification code is: {$verificationCode}
If you weren't the person who requested this reset, please send a reply to this e-mail.
MSG;
$message = mail_compose(
[$forgotUser['email'] => $forgotUser['username']],
'Flashii Password Reset',
$messageBody
);
if (!mail_send($message)) {
tpl_var('auth_forgot_error', 'Failed to send reset email, please contact the administrator.');
user_recovery_token_invalidate($forgotUser['user_id'], $verificationCode);
break;
}
}
header(sprintf('Location: %s', url('auth-reset', ['user' => $forgotUser['user_id']])));
break;
}
echo tpl_render('auth.auth');
header('Location: ' . url('auth-forgot'));
break;
case 'login':
if (user_session_active()) {
header('Location: ' . url('index'));
break;
}
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
$authLoginError = '';
while ($isSubmission) {
$ipAddress = ip_remote_address();
if (!isset($authUsername, $authPassword)) {
$authLoginError = "You didn't fill all the forms!";
break;
}
$remainingAttempts = user_login_attempts_remaining($ipAddress);
if ($remainingAttempts < 1) {
$authLoginError = 'Too many failed login attempts, try again later.';
break;
}
if (!csrf_verify('login', $_POST['csrf'] ?? '')) {
$authLoginError = 'Possible request forgery detected, refresh and try again.';
break;
}
$userData = user_find_for_login($authUsername);
$loginFailedError = sprintf(
"Invalid username or password, %d attempt%s remaining.",
$remainingAttempts - 1,
$remainingAttempts === 2 ? '' : 's'
);
if (empty($userData) || $userData['user_id'] < 1) {
user_login_attempt_record(false, null, $ipAddress, $userAgent);
$authLoginError = $loginFailedError;
break;
}
if (!password_verify($authPassword, $userData['password'])) {
user_login_attempt_record(false, $userData['user_id'], $ipAddress, $userAgent);
$authLoginError = $loginFailedError;
break;
}
user_login_attempt_record(true, $userData['user_id'], $ipAddress, $userAgent);
if ($loginPermission > 0) {
$generalPerms = perms_get_user(MSZ_PERMS_GENERAL, $userData['user_id']);
if (!perms_check($generalPerms, $loginPermission)) {
$authLoginError = 'Your credentials were correct, but your account lacks the proper permissions to use this website.';
break;
}
}
$sessionKey = user_session_create($userData['user_id'], $ipAddress, $userAgent);
if ($sessionKey === '') {
$authLoginError = 'Unable to create new session, contact an administrator ASAP.';
break;
}
user_session_start($userData['user_id'], $sessionKey);
$cookieLife = strtotime(user_session_current('session_expires'));
$cookieValue = base64url_encode(user_session_cookie_pack($userData['user_id'], $sessionKey));
setcookie('msz_auth', $cookieValue, $cookieLife, '/', '', true, true);
if (!is_local_url($authRedirect)) {
$authRedirect = url('index');
}
header("Location: {$authRedirect}");
return;
}
if (!empty($authLoginError)) {
tpl_var('auth_login_error', $authLoginError);
} elseif ($siteIsPrivate) {
tpl_var('auth_register_message', config_get_default('', 'Private', 'message'));
}
echo tpl_render('auth.auth');
default:
header('Location: ' . url('auth-login'));
break;
case 'register':
if (user_session_active()) {
header('Location: ' . url('index'));
}
$authRegistrationError = '';
while ($isSubmission) {
if (!$canCreateAccount || $authRestricted) {
$authRegistrationError = 'You may not create an account right now.';
break;
}
if (!isset($authUsername, $authPassword, $authEmail)) {
$authRegistrationError = "You didn't fill all the forms!";
break;
}
if (!csrf_verify('register', $_POST['csrf'] ?? '')) {
$authRegistrationError = 'Possible request forgery detected, refresh and try again.';
break;
}
$checkSpamBot = mb_strtolower($_POST['auth']['meow'] ?? '');
$spamBotValid = [
'19', '21', 'nineteen', 'nine-teen', 'nine teen', 'twentyone', 'twenty-one', 'twenty one',
];
if (!in_array($checkSpamBot, $spamBotValid)) {
$authRegistrationError = 'Human only cool club, robots begone.';
break;
}
$usernameValidation = user_validate_username($authUsername, true);
if ($usernameValidation !== '') {
$authRegistrationError = MSZ_USER_USERNAME_VALIDATION_STRINGS[$usernameValidation];
break;
}
$emailValidation = user_validate_email($authEmail, true);
if ($emailValidation !== '') {
$authRegistrationError = $emailValidation === 'in-use'
? 'This e-mail address has already been used!'
: 'The e-mail address you entered is invalid!';
break;
}
if (user_validate_password($authPassword) !== '') {
$authRegistrationError = 'Your password is too weak!';
break;
}
$createUser = user_create(
$authUsername,
$authPassword,
$authEmail,
ip_remote_address()
);
if ($createUser < 1) {
$authRegistrationError = 'Something happened?';
break;
}
user_role_add($createUser, MSZ_ROLE_MAIN);
tpl_var('auth_register_message', 'Welcome to Flashii! You may now log in.');
break;
}
if (!empty($authRegistrationError)) {
tpl_var('auth_register_error', $authRegistrationError);
}
echo tpl_render('auth.auth');
header('Location: ' . url('auth-register'));
break;
}

2
public/auth/index.php Normal file
View file

@ -0,0 +1,2 @@
<?php
header('Location: /auth/login.php');

109
public/auth/login.php Normal file
View file

@ -0,0 +1,109 @@
<?php
use Misuzu\Request\RequestVar;
require_once '../../misuzu.php';
if (user_session_active()) {
header(sprintf('Location: %s', url('index')));
return;
}
if (isset(RequestVar::get()->resolve_user)) {
header('Content-Type: text/plain; charset=utf-8');
echo user_id_from_username(RequestVar::get()->resolve_user->value('string'));
return;
}
$login = RequestVar::post()->login;
$notices = [];
$siteIsPrivate = boolval(config_get_default(false, 'Private', 'enabled'));
$loginPermission = $siteIsPrivate ? intval(config_get_default(0, 'Private', 'permission')) : 0;
$ipAddress = ip_remote_address();
$remainingAttempts = user_login_attempts_remaining($ipAddress);
while (!empty($login->value('array'))) {
if (!csrf_verify('login', $_POST['csrf'] ?? '')) {
$notices[] = 'Was unable to verify the request, please try again!';
break;
}
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
if ($login->username->empty() || $login->password->empty()) {
$notices[] = "You didn't fill in a username and/or password.";
break;
}
if ($remainingAttempts < 1) {
$notices[] = "There are too many failed login attempts from your IP address, please try again later.";
break;
}
$userData = user_find_for_login($login->username->value('string', ''));
$loginFailedError = sprintf(
"Invalid username or password, %d attempt%s remaining.",
$remainingAttempts - 1,
$remainingAttempts === 2 ? '' : 's'
);
if (empty($userData) || $userData['user_id'] < 1) {
user_login_attempt_record(false, null, $ipAddress, $userAgent);
$notices[] = $loginFailedError;
break;
}
if (!password_verify($login->password->value('string', ''), $userData['password'])) {
user_login_attempt_record(false, $userData['user_id'], $ipAddress, $userAgent);
$notices[] = $loginFailedError;
break;
}
user_login_attempt_record(true, $userData['user_id'], $ipAddress, $userAgent);
if ($loginPermission > 0 && !perms_check_user(MSZ_PERMS_GENERAL, $userData['user_id'], $loginPermission)) {
$notices[] = "Login succeeded, but you're not allowed to browse the site right now.";
break;
}
$sessionKey = user_session_create($userData['user_id'], $ipAddress, $userAgent);
if (empty($sessionKey)) {
$notices[] = "Something broke while creating a session for you, please tell an administrator or developer about this!";
break;
}
user_session_start($userData['user_id'], $sessionKey);
$cookieLife = strtotime(user_session_current('session_expires'));
$cookieValue = base64url_encode(user_session_cookie_pack($userData['user_id'], $sessionKey));
setcookie('msz_auth', $cookieValue, $cookieLife, '/', '', true, true);
$redirect = $login->redirect->value('string', '');
if (!is_local_url($redirect)) {
$redirect = url('index');
}
header("Location: {$redirect}");
return;
}
$welcomeMode = RequestVar::get()->welcome->value('bool', false);
$loginUsername = $login->username->value('string') ?? RequestVar::get()->username->value('string', '');
$loginRedirect = $welcomeMode ? '/' : RequestVar::get()->redirect->value('string') ?? $_SERVER['HTTP_REFERER'] ?? '/';
$sitePrivateMessage = $siteIsPrivate ? config_get_default('', 'Private', 'message') : '';
$canResetPassword = $siteIsPrivate ? boolval(config_get_default(false, 'Private', 'password_reset')) : true;
$canRegisterAccount = !$siteIsPrivate;
echo tpl_render('auth.login', [
'login_notices' => $notices,
'login_username' => $loginUsername,
'login_redirect' => $loginRedirect,
'login_can_reset_password' => $canResetPassword,
'login_can_register' => $canRegisterAccount,
'login_attempts_remaining' => $remainingAttempts,
'login_welcome' => $welcomeMode,
'login_private' => [
'enabled' => $siteIsPrivate,
'message' => $sitePrivateMessage,
],
]);

18
public/auth/logout.php Normal file
View file

@ -0,0 +1,18 @@
<?php
use Misuzu\Request\RequestVar;
require_once '../../misuzu.php';
if (!user_session_active()) {
header(sprintf('Location: %s', url('index')));
return;
}
if (csrf_verify('logout', RequestVar::get()->token->value('string', ''))) {
setcookie('msz_auth', '', -9001, '/', '', true, true);
user_session_stop(true);
header(sprintf('Location: %s', url('index')));
return;
}
echo tpl_render('auth.logout');

134
public/auth/password.php Normal file
View file

@ -0,0 +1,134 @@
<?php
use Misuzu\Request\RequestVar;
require_once '../../misuzu.php';
if (user_session_active()) {
header(sprintf('Location: %s', url('settings-mode', ['mode' => 'account'])));
return;
}
$reset = RequestVar::post()->reset;
$forgot = RequestVar::post()->forgot;
$userId = $reset->user->value('int') ?? RequestVar::get()->user->value('int', 0);
$username = $userId > 0 ? user_username_from_id($userId) : '';
if ($userId > 0 && empty($username)) {
header(sprintf('Location: %s', url('auth-forgot')));
return;
}
$notices = [];
$siteIsPrivate = boolval(config_get_default(false, 'Private', 'enabled'));
$canResetPassword = $siteIsPrivate ? boolval(config_get_default(false, 'Private', 'password_reset')) : true;
$ipAddress = ip_remote_address();
$remainingAttempts = user_login_attempts_remaining($ipAddress);
while ($canResetPassword) {
if (!empty($reset->value('array', null)) && $userId > 0) {
if (!csrf_verify('passreset', $_POST['csrf'] ?? '')) {
$notices[] = 'Was unable to verify the request, please try again!';
break;
}
$verificationCode = $reset->verification->value('string', '');
if (!user_recovery_token_validate($userId, $verificationCode)) {
$notices[] = 'Invalid verification code!';
break;
}
$passwordNew = $reset->password->new->value('string', '');
$passwordConfirm = $reset->password->confirm->value('string', '');
if (empty($passwordNew) || empty($passwordConfirm)
|| $passwordNew !== $passwordConfirm) {
$notices[] = "Password confirmation failed!";
break;
}
if (user_validate_password($passwordNew) !== '') {
$notices[] = 'Your password is too weak!';
break;
}
if (user_password_set($userId, $passwordNew)) {
audit_log(MSZ_AUDIT_PASSWORD_RESET, $userId);
} else {
throw new UnexpectedValueException('Password reset failed.');
}
user_recovery_token_invalidate($userId, $verificationCode);
header(sprintf('Location: %s', url('auth-login', ['redirect' => '/'])));
return;
}
if (!empty($forgot->value('array', null))) {
if (!csrf_verify('passforgot', $_POST['csrf'] ?? '')) {
$notices[] = 'Was unable to verify the request, please try again!';
break;
}
if ($forgot->email->empty()) {
$notices[] = "You didn't supply an e-mail address.";
break;
}
if ($remainingAttempts < 1) {
$notices[] = "There are too many failed login attempts from your IP address, please try again later.";
break;
}
$forgotUser = user_find_for_reset($forgot->email->value('string'));
if (empty($forgotUser)) {
$notices[] = "This e-mail address is not registered with us.";
break;
}
if (!user_recovery_token_sent($forgotUser['user_id'], $ipAddress)) {
$verificationCode = user_recovery_token_create($forgotUser['user_id'], $ipAddress);
if (empty($verificationCode)) {
throw new UnexpectedValueException('A verification code failed to insert.');
}
$messageBody = <<<MSG
Hey {$forgotUser['username']},
You, or someone pretending to be you, has requested a password reset for your account.
Your verification code is: {$verificationCode}
If you weren't the person who requested this reset, please send a reply to this e-mail.
MSG;
$message = mail_compose(
[$forgotUser['email'] => $forgotUser['username']],
'Flashii Password Reset',
$messageBody
);
if (!mail_send($message)) {
$notices[] = "Failed to send reset email, please contact the administrator.";
user_recovery_token_invalidate($forgotUser['user_id'], $verificationCode);
break;
}
}
header(sprintf('Location: %s', url('auth-reset', ['user' => $forgotUser['user_id']])));
return;
}
break;
}
echo tpl_render($userId > 0 ? 'auth.password_reset' : 'auth.password_forgot', [
'password_notices' => $notices,
'password_email' => $forgot->email->value('string', ''),
'password_attempts_remaining' => $remainingAttempts,
'password_user_id' => $userId,
'password_username' => $username,
'password_verification' => $verificationCode ?? '',
]);

89
public/auth/register.php Normal file
View file

@ -0,0 +1,89 @@
<?php
use Misuzu\Request\RequestVar;
require_once '../../misuzu.php';
if (user_session_active()) {
header(sprintf('Location: %s', url('index')));
return;
}
$register = RequestVar::post()->register;
$notices = [];
$ipAddress = ip_remote_address();
$remainingAttempts = user_login_attempts_remaining($ipAddress);
$restricted = ip_blacklist_check(ip_remote_address()) ? 'blacklist'
: (user_warning_check_ip(ip_remote_address()) ? 'ban' : '');
while (!$restricted && !empty($register->value('array'))) {
if (!csrf_verify('register', $_POST['csrf'] ?? '')) {
$notices[] = 'Was unable to verify the request, please try again!';
break;
}
if ($remainingAttempts < 1) {
$notices[] = "There are too many failed login attempts from your IP address, you may not create an account right now.";
break;
}
if ($register->username->empty() || $register->password->empty() || $register->email->empty() || $register->question->empty()) {
$notices[] = "You haven't filled in all fields.";
break;
}
$checkSpamBot = mb_strtolower($register->question->value('string', ''));
$spamBotValid = [
'19', '21', 'nineteen', 'nine-teen', 'nine teen', 'twentyone', 'twenty-one', 'twenty one',
];
if (!in_array($checkSpamBot, $spamBotValid)) {
$notices[] = 'Human only cool club, robots begone.';
break;
}
$username = $register->username->value('string', '');
$usernameValidation = user_validate_username($username, true);
if ($usernameValidation !== '') {
$notices[] = MSZ_USER_USERNAME_VALIDATION_STRINGS[$usernameValidation];
}
$email = $register->email->value('string', '');
$emailValidation = user_validate_email($email, true);
if ($emailValidation !== '') {
$notices[] = $emailValidation === 'in-use'
? 'This e-mail address has already been used!'
: 'The e-mail address you entered is invalid!';
}
$password = $register->password->value('string', '');
if (user_validate_password($password) !== '') {
$notices[] = 'Your password is too weak!';
}
if (!empty($notices)) {
break;
}
$createUser = user_create(
$username,
$password,
$email,
$ipAddress
);
if ($createUser < 1) {
$notices[] = 'Something went wrong while creating your account, please alert an administrator or a developer about this!';
break;
}
user_role_add($createUser, MSZ_ROLE_MAIN);
header(sprintf('Location: %s', url('auth-login-welcome', ['username' => $username])));
return;
}
echo tpl_render('auth.register', [
'register_notices' => $notices,
'register_username' => $register->username->value('string', ''),
'register_email' => $register->email->value('string', ''),
'register_restricted' => $restricted,
]);

View file

@ -23,12 +23,22 @@ class RequestVar
return new static($_POST ?? []);
}
public static function request(): RequestVar
{
return new static($_REQUEST);
}
public function __get(string $name)
{
return $this->select($name);
}
public function __isset(string $name): bool
{
return $this->isset($name);
}
public function isset(string $name): bool
{
switch ($this->type) {
case 'array':
@ -38,32 +48,37 @@ class RequestVar
return isset($this->value->{$name});
default:
return null;
return !is_null($this->value);
}
}
public function empty(): bool
{
return empty($this->value);
}
public function select(string $name): RequestVar
{
switch ($this->type) {
case 'array':
return new static($this->value[$name]);
return new static($this->value[$name] ?? []);
case 'object':
return new static($this->value->{$name});
return new static($this->value->{$name} ?? new \stdClass);
default:
return null;
return new static(null);
}
}
public function value(string $type = 'string')
public function value(string $type = 'string', $default = null)
{
if (!is_null($this->valueCasted)) {
$this->valueCasted;
}
if ($this->type === 'NULL' || (($type === 'object' || $type === 'array') && $this->type !== $type)) {
return null;
return $default;
}
if ($type !== 'string' && $this->type === 'string') {
@ -79,7 +94,7 @@ class RequestVar
return (float)$this->value;
}
} elseif ($type !== $this->type) {
return null;
return $default;
}
return $this->valueCasted = $this->value;

View file

@ -12,12 +12,13 @@ define('MSZ_URLS', [
'info' => ['/info.php/<title>'],
'media-proxy' => ['/proxy.php/<hash>/<url>'],
'auth-login' => ['/auth.php', ['m' => 'login']],
'auth-register' => ['/auth.php', ['m' => 'register']],
'auth-forgot' => ['/auth.php', ['m' => 'forgot']],
'auth-reset' => ['/auth.php', ['m' => 'reset', 'u' => '<user>']],
'auth-logout' => ['/auth.php', ['m' => 'logout', 's' => '{logout}']],
'auth-resolve-user' => ['/auth.php', ['m' => 'get_user', 'u' => '<username>']],
'auth-login' => ['/auth/login.php', ['username' => '<username>', 'redirect' => '<redirect>']],
'auth-login-welcome' => ['/auth/login.php', ['welcome' => '1', 'username' => '<username>']],
'auth-register' => ['/auth/register.php'],
'auth-forgot' => ['/auth/password.php'],
'auth-reset' => ['/auth/password.php', ['user' => '<user>']],
'auth-logout' => ['/auth/logout.php', ['token' => '{logout}']],
'auth-resolve-user' => ['/auth/login.php', ['resolve_user' => '<username>']],
'changelog-index' => ['/changelog.php'],
'changelog-change' => ['/changelog.php', ['c' => '<change>']],

View file

@ -82,7 +82,6 @@
'title': 'Log in',
'url': url('auth-login'),
'icon': 'fas fa-sign-in-alt fa-fw',
'class': 'js-login-button',
},
]
%}
@ -127,7 +126,7 @@
<a href="{{ url('user-profile', {'user': current_user.user_id}) }}" class="avatar header__desktop__user__avatar" title="{{ current_user.username }}"
style="background-image:url('{{ url('user-avatar', {'user': current_user.user_id}) }}');{{ current_user.user_colour|html_colour }}"></a>
{% else %}
<a href="{{ url('auth-login') }}" class="avatar header__desktop__user__avatar js-login-button"
<a href="{{ url('auth-login') }}" class="avatar header__desktop__user__avatar"
style="background-image:url('{{ url('user-avatar') }}');"></a>
{% endif %}
</div>

View file

@ -1,75 +0,0 @@
{% extends 'auth/master.twig' %}
{% from 'macros.twig' import container_title %}
{% from '_layout/input.twig' import input_hidden, input_csrf, input_text %}
{% from 'auth/macros.twig' import auth_login %}
{% block content %}
{{ auth_login(
auth_username|default(''),
auth_register_message|default(auth_login_error|default('')),
auth_register_message is defined,
auth_redirect|default('/'),
auth_mode == 'login'
) }}
{% if can_create_account %}
<form class="container auth" method="post" action="">
{{ input_hidden('auth[mode]', 'register') }}
{{ input_csrf('register') }}
{{ container_title('<i class="fas fa-user-check fa-fw"></i> Register') }}
{% if auth_restricted %}
<div class="warning auth__warning">
<div class="warning__content">
{% if auth_restricted == 2 %}
A user is currently in a banned and/or silenced state from the same IP address you're currently visiting the site from. If said user isn't you and you wish to create an account, please <a href="{{ url('info', {'title': 'contact'}) }}" class="warning__link">contact us</a>!
{% else %}
The IP address from which you are visiting the website appears on our blacklist, you are not allowed to register from this address but if you already have an account you can log in just fine using the form above. If you think this blacklisting is a mistake, please <a href="{{ url('info', {'title': 'contact'}) }}" class="warning__link">contact us</a>!
{% endif %}
</div>
</div>
{% else %}
{% if auth_register_error is defined %}
<div class="warning auth__warning">
<div class="warning__content">
{{ auth_register_error }}
</div>
</div>
{% endif %}
<div class="auth__form">
{{ input_text('auth[username]', 'auth__input', auth_username|default(''), 'text', 'Username', true, null, 0, auth_mode == 'register') }}
{{ input_text('auth[password]', 'auth__input', '', 'password', 'Password', true) }}
{{ input_text('auth[email]', 'auth__input', auth_email|default(''), 'text', 'E-mail', true) }}
{{ input_text('auth[meow]', 'auth__input', '', 'text', 'What is the outcome of nine plus ten?', true) }}
<button class="input__button">Create your account</button>
</div>
{% endif %}
</form>
{% endif %}
{% if can_reset_password %}
<form class="container auth" method="post" action="">
{{ input_hidden('auth[mode]', 'forgot') }}
{{ input_csrf('passforgot') }}
{{ container_title('<i class="fas fa-user-lock fa-fw"></i> Forgot password') }}
{% if auth_forgot_error is defined %}
<div class="warning auth__warning">
<div class="warning__content">
{{ auth_forgot_error }}
</div>
</div>
{% endif %}
<div class="auth__form">
{{ input_text('auth[email]', 'auth__input', auth_email|default(''), 'text', 'E-mail', true, null, 0, auth_mode == 'forgot') }}
<button class="input__button">Send reminder</button>
</div>
</form>
{% endif %}
{% endblock %}

View file

@ -1,12 +0,0 @@
{% extends 'master_minimal.twig' %}
{% from 'macros.twig' import container_title %}
{% block content %}
<div class="container auth">
{{ container_title(message_title|default('<i class="fas fa-lock fa-fw"></i> Unavailable')) }}
<div class="container__content">
{{ message|default('The site is currently unavailable, try again later.')|raw }}
</div>
</div>
{% endblock %}

62
templates/auth/login.twig Normal file
View file

@ -0,0 +1,62 @@
{% extends 'auth/master.twig' %}
{% from '_layout/input.twig' import input_hidden, input_csrf, input_text %}
{% set title = 'Login' %}
{% block content %}
<form class="container auth__container auth__login js-login-form" method="post" action="{{ url('auth-login') }}">
{{ input_csrf('login') }}
{{ input_hidden('login[redirect]', login_redirect) }}
<div class="container__title">
<div class="container__title__background"></div>
<div class="auth__login__header">
<div class="avatar auth__login__avatar js-login-avatar" style="background-image:url('{{ url('user-avatar') }}');"></div>
</div>
</div>
{% if login_notices|length > 0 %}
<div class="warning auth__warning">
<div class="warning__content">
{% for notice in login_notices %}
<p class="auth__warning__paragraph">{{ notice }}</p>
{% endfor %}
</div>
</div>
{% elseif login_welcome %}
<div class="warning auth__warning auth__warning--welcome">
<div class="warning__content">
<p class="auth__warning__paragraph">Welcome to Flashii, you may now log in!</p>
</div>
</div>
{% endif %}
<label class="auth__label">
<div class="auth__label__text">
Username
</div>
<div class="auth__label__value">
{{ input_text('login[username]', 'auth__label__input js-login-username', login_username, 'text', '', true, null, 1, not login_welcome) }}
</div>
</label>
<label class="auth__label">
<div class="auth__label__text">
Password
{% if login_can_reset_password %}
<a href="{{ url('auth-forgot') }}" class="auth__label__action" tabindex="4">Forgot?</a>
{% endif %}
</div>
<div class="auth__label__value">
{{ input_text('login[password]', 'auth__label__input', '', 'password', '', true, null, 2, login_welcome) }}
</div>
</label>
<div class="auth__buttons">
<button class="input__button auth__buttons__button" tabindex="3">Log in</button>
{% if login_can_register %}
<a href="{{ url('auth-register') }}" class="input__button auth__buttons__button auth__buttons__button--minor" tabindex="5">Create an account</a>
{% endif %}
</div>
</form>
{% endblock %}

View file

@ -1,14 +1,16 @@
{% extends 'auth/master.twig' %}
{% from 'macros.twig' import container_title %}
{% set title = 'Logout confirmation' %}
{% block content %}
<div class="container auth">
<div class="container auth__container">
{{ container_title('<i class="fas fa-user-clock fa-fw"></i> Logout confirmation') }}
<div class="auth__form">
<p class="auth__paragraph">We couldn't verify that you were actually the person attempting to log out.</p>
<p class="auth__paragraph">Press the button below to verify the logout request, otherwise click back in your browser or close this tab.</p>
<p class="auth__paragraph">This error is usually caused by pressing the logout button on a page that's been loaded for a while.</p>
<div class="auth__logout">
<p class="auth__logout__paragraph">We couldn't verify that you were actually the person attempting to log out.</p>
<p class="auth__logout__paragraph">Press the button below to verify the logout request, otherwise click back in your browser or close this tab.</p>
<p class="auth__logout__paragraph">This error is usually caused by pressing the logout button on a page that's been loaded for a while.</p>
<a href="{{ url('auth-logout') }}" class="input__button">Log out</a>
</div>
</div>

View file

@ -1,39 +0,0 @@
{% macro auth_login(username, message, is_welcome, redirect, autofocus) %}
{% set is_welcome = is_welcome|default(false) %}
{% set autofocus = autofocus|default(false) %}
{% from '_layout/input.twig' import input_hidden, input_csrf, input_text %}
<form class="container auth js-login-form" method="post" action="{{ url('auth-login') }}">
{{ input_hidden('auth[mode]', 'login') }}
{{ input_csrf('login') }}
{% if redirect|length > 0 %}
{{ input_hidden('auth[redirect]', redirect) }}
{% endif %}
<div class="container__title">
<div class="container__title__background"></div>
<div class="auth__header">
<div class="avatar auth__avatar js-login-avatar" style="background-image:url('{{ url('user-avatar') }}');"></div>
</div>
</div>
{% if message|length > 0 %}
<div class="warning auth__warning{% if is_welcome %} auth__warning--welcome{% endif %}">
<div class="warning__content">
{{ message }}
</div>
</div>
{% endif %}
<div class="auth__form">
{{ input_text('auth[username]', 'auth__input js-login-username', username|default(''), 'text', 'Username', true, null, 0, autofocus) }}
{{ input_text('auth[password]', 'auth__input', '', 'password', 'Password', true, null) }}
<div class="auth__buttons">
<button class="input__button auth__button">Log in</button>
</div>
</div>
</form>
{% endmacro %}

View file

@ -1,36 +0,0 @@
{% extends 'auth/master.twig' %}
{% from 'macros.twig' import container_title %}
{% from '_layout/input.twig' import input_hidden, input_csrf, input_text %}
{% block content %}
<form class="container auth" method="post" action="">
{{ input_hidden('auth[mode]', 'reset') }}
{{ input_hidden('auth[user]', reset_user.user_id) }}
{{ input_csrf('passreset') }}
{{ container_title('<i class="fas fa-user-lock fa-fw"></i> Resetting password for ' ~ reset_user.username) }}
<div class="warning auth__warning{% if auth_reset_error is not defined %} auth__warning--welcome{% endif %}">
<div class="warning__content">
{{ auth_reset_error|default(auth_reset_message|default('')) }}
</div>
</div>
<div class="auth__form">
{{ input_text(
'auth[verification]',
'input__text--monospace auth__input',
reset_verify|default(''),
reset_verify is defined ? 'hidden' : 'text',
'verification code',
true,
{'maxlength':12}
) }}
{{ input_text('auth[password][new]', 'auth__input', '', 'password', 'new password', true) }}
{{ input_text('auth[password][confirm]', 'auth__input', '', 'password', 'confirm password', true) }}
<button class="input__button">Change password</button>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,36 @@
{% extends 'auth/master.twig' %}
{% from 'macros.twig' import container_title %}
{% from '_layout/input.twig' import input_hidden, input_csrf, input_text %}
{% set title = 'Forgot password' %}
{% block content %}
<form class="container auth__container auth__password" method="post" action="{{ url('auth-forgot') }}">
{{ container_title('<i class="fas fa-user-lock fa-fw"></i> Forgot password') }}
{{ input_csrf('passforgot') }}
{% if password_notices|length > 0 %}
<div class="warning auth__warning">
<div class="warning__content">
{% for notice in password_notices %}
<p class="auth__warning__paragraph">{{ notice }}</p>
{% endfor %}
</div>
</div>
{% endif %}
<label class="auth__label">
<div class="auth__label__text">
E-mail
</div>
<div class="auth__label__value">
{{ input_text('forgot[email]', 'auth__label__input', password_email, 'text', '', true, null, 1) }}
</div>
</label>
<div class="auth__buttons">
<button class="input__button auth__buttons__button" tabindex="2">Send Reminder</button>
<a href="{{ url('auth-login') }}" class="input__button auth__buttons__button auth__buttons__button--minor" tabindex="3">Log in</a>
</div>
</form>
{% endblock %}

View file

@ -0,0 +1,66 @@
{% extends 'auth/master.twig' %}
{% from 'macros.twig' import container_title %}
{% from '_layout/input.twig' import input_hidden, input_csrf, input_text %}
{% set title = 'Resetting password' %}
{% block content %}
<form class="container auth__container auth__password" method="post" action="{{ url('auth-reset') }}">
{{ container_title('<i class="fas fa-user-lock fa-fw"></i> Resetting password for ' ~ password_username) }}
{{ input_hidden('reset[user]', password_user_id) }}
{{ input_csrf('passreset') }}
{% if password_notices|length > 0 %}
<div class="warning auth__warning">
<div class="warning__content">
{% for notice in password_notices %}
<p class="auth__warning__paragraph">{{ notice }}</p>
{% endfor %}
</div>
</div>
{% else %}
<div class="warning auth__warning auth__warning--welcome">
<div class="warning__content">
<p class="auth__warning__paragraph">A verification code should've been sent to your e-mail address.</p>
</div>
</div>
{% endif %}
{% if password_verification|length == 12 %}
{{ input_hidden('reset[verification]', password_verification) }}
{% else %}
<label class="auth__label">
<div class="auth__label__text">
Verification Code
</div>
<div class="auth__label__value">
{{ input_text('reset[verification]', 'input__text--monospace auth__label__input', '', 'text', '', true, {'maxlength':12}, 1) }}
</div>
</label>
{% endif %}
<label class="auth__label">
<div class="auth__label__text">
New Password
</div>
<div class="auth__label__value">
{{ input_text('reset[password][new]', 'auth__label__input', '', 'password', '', true, null, 2) }}
</div>
</label>
<label class="auth__label">
<div class="auth__label__text">
Confirm Password
</div>
<div class="auth__label__value">
{{ input_text('reset[password][confirm]', 'auth__label__input', '', 'password', '', true, null, 3) }}
</div>
</label>
<div class="auth__buttons">
<button class="input__button auth__buttons__button" tabindex="4">Change Password</button>
<a href="{{ url('auth-login') }}" class="input__button auth__buttons__button auth__buttons__button--minor" tabindex="5">Log in</a>
</div>
</div>
{% endblock %}

View file

@ -1,10 +0,0 @@
{% extends 'auth/master.twig' %}
{% from 'auth/macros.twig' import auth_login %}
{% block content %}
{{ auth_login(
auth_username|default(''),
auth_login_error|default(private_message|default('')),
auth_login_error is not defined
) }}
{% endblock %}

View file

@ -0,0 +1,90 @@
{% extends 'auth/master.twig' %}
{% from 'macros.twig' import container_title %}
{% from '_layout/input.twig' import input_hidden, input_csrf, input_text %}
{% set title = 'Register' %}
{% block content %}
<form class="container auth__container auth__register" method="post" action="{{ url('auth-register') }}">
{{ container_title('<i class="fas fa-user-check fa-fw"></i> Register') }}
<div class="auth__register__container">
{% if not register_restricted %}
<div class="auth__register__info">
{% if register_notices|length > 0 %}
<div class="warning auth__warning">
<div class="warning__content">
{% for notice in register_notices %}
<p class="auth__warning__paragraph">{{ notice }}</p>
{% endfor %}
</div>
</div>
{% endif %}
<p class="auth__register__paragraph">Welcome to Flashii! Before creating your account, here are a few things you should take note of.</p>
<p class="auth__register__paragraph">By creating an account you agree to the <a href="{{ url('info', {'title': 'rules'}) }}" class="auth__register__link">rules</a>.</p>
<p class="auth__register__paragraph">Engaging in borderline illegal activity on platforms provided by Flashii will result in a permanent ban, as described by Global Rule 5.</p>
<p class="auth__register__paragraph">You are not allowed to have more than one account unless given explicit permission, as described by Global Rule 6.</p>
<p class="auth__register__paragraph">You must be at least 13 years of age to use this website, as described by Global Rule 8.</p>
</div>
{% endif %}
<div class="auth__register__form">
{{ input_csrf('register') }}
{% if register_restricted %}
<div class="warning auth__warning">
<div class="warning__content">
{% if register_restricted == 'ban' %}
<p class="auth__warning__paragraph">A user is currently in a banned and/or silenced state from the same IP address you're currently visiting the site from. If said user isn't you and you wish to create an account, please <a href="{{ url('info', {'title': 'contact'}) }}" class="warning__link">contact us</a>!</p>
{% else %}
<p class="auth__warning__paragraph">The IP address from which you are visiting the website appears on our blacklist, you are not allowed to register from this address but if you already have an account you can log in just fine using the form above. If you think this blacklisting is a mistake, please <a href="{{ url('info', {'title': 'contact'}) }}" class="warning__link">contact us</a>!</p>
{% endif %}
</div>
</div>
{% else %}
<label class="auth__label">
<div class="auth__label__text">
Username
</div>
<div class="auth__label__value">
{{ input_text('register[username]', 'auth__label__input', register_username, 'text', '', true, null, 1, true) }}
</div>
</label>
<label class="auth__label">
<div class="auth__label__text">
Password
</div>
<div class="auth__label__value">
{{ input_text('register[password]', 'auth__label__input', '', 'password', '', true, null, 2) }}
</div>
</label>
<label class="auth__label">
<div class="auth__label__text">
E-mail
</div>
<div class="auth__label__value">
{{ input_text('register[email]', 'auth__label__input', register_email, 'text', '', true, null, 3) }}
</div>
</label>
<label class="auth__label">
<div class="auth__label__text">
What is the outcome of nine plus ten?
</div>
<div class="auth__label__value">
{{ input_text('register[question]', 'auth__label__input', '', 'text', '', true, null, 4) }}
</div>
</label>
<div class="auth__buttons">
<button class="input__button auth__buttons__button" tabindex="5">Create your account</button>
<a href="{{ url('auth-login') }}" class="input__button auth__buttons__button auth__buttons__button--minor" tabindex="6">Log in</a>
</div>
{% endif %}
</div>
</div>
</form>
{% endblock %}