Massively cleaned up settings.php.

This commit is contained in:
flash 2018-10-29 20:12:06 +01:00
parent 1430b2e692
commit 67d59d90bf
8 changed files with 269 additions and 209 deletions

View file

@ -1,33 +1,23 @@
<?php
require_once '../misuzu.php';
$queryOffset = (int)($_GET['o'] ?? 0);
$queryTake = 15;
if (!user_session_active()) {
echo render_error(403);
return;
}
$settingsUserId = user_session_current('user_id', 0);
tpl_vars([
'settings_user_id' => $settingsUserId,
]);
$settingsErrors = [];
$errors = [];
$disableAccountOptions = !MSZ_DEBUG && (
boolval(config_get_default(false, 'Private', 'enabled'))
&& boolval(config_get_default(false, 'Private', 'disable_account_settings'))
);
$avatarFileName = "{$settingsUserId}.msz";
$avatarProps = user_avatar_default_options();
$backgroundProps = user_background_default_options();
$currentEmail = user_email_get(user_session_current('user_id'));
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!csrf_verify('settings', $_POST['csrf'] ?? '')) {
$settingsErrors[] = MSZ_TMP_USER_ERROR_STRINGS['csrf'];
$errors[] = MSZ_TMP_USER_ERROR_STRINGS['csrf'];
} else {
if (!empty($_POST['session'])) {
$currentSessionKilled = false;
@ -37,22 +27,22 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$sessionId = intval($sessionId);
$session = user_session_find($sessionId);
if (!$session || (int)$session['user_id'] !== $settingsUserId) {
$settingsErrors[] = "Session #{$sessionId} does not exist.";
if (!$session || (int)$session['user_id'] !== user_session_current('user_id')) {
$errors[] = "Session #{$sessionId} does not exist.";
break;
} elseif ((int)$session['session_id'] === user_session_current('session_id')) {
$currentSessionKilled = true;
}
user_session_delete($session['session_id']);
audit_log('PERSONAL_SESSION_DESTROY', $settingsUserId, [
audit_log('PERSONAL_SESSION_DESTROY', user_session_current('user_id'), [
$session['session_id'],
]);
}
} elseif ($_POST['session'] === 'all') {
$currentSessionKilled = true;
user_session_purge_all($settingsUserId);
audit_log('PERSONAL_SESSION_DESTROY_ALL', $settingsUserId);
user_session_purge_all(user_session_current('user_id'));
audit_log('PERSONAL_SESSION_DESTROY_ALL', user_session_current('user_id'));
}
if ($currentSessionKilled) {
@ -62,85 +52,58 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
}
if (!$disableAccountOptions) {
if (!empty($_POST['current_password'])
|| (
(isset($_POST['password']) || isset($_POST['email']))
&& (!empty($_POST['password']['new']) || !empty($_POST['email']['new']))
)
) {
$updateAccountFields = [];
$currentPasswordValid = !empty($_POST['current_password']);
$fetchPassword = db_prepare('
SELECT `password`
FROM `msz_users`
WHERE `user_id` = :user_id
');
$fetchPassword->bindValue('user_id', $settingsUserId);
$currentPassword = $fetchPassword->execute() ? $fetchPassword->fetchColumn() : null;
if (empty($currentPassword)) {
$settingsErrors[] = 'Something went horribly wrong.';
} else {
if (!password_verify($_POST['current_password'], $currentPassword)) {
$settingsErrors[] = 'Your current password was incorrect.';
if (!user_password_verify_db(user_session_current('user_id'), $_POST['current_password'] ?? '')) {
$errors[] = 'Your password was incorrect.';
} else {
// Changing e-mail
if (!empty($_POST['email']['new'])) {
if (empty($_POST['email']['confirm']) || $_POST['email']['new'] !== $_POST['email']['confirm']) {
$errors[] = 'The addresses you entered did not match each other.';
} elseif ($currentEmail === mb_strtolower($_POST['email']['confirm'])) {
$errors[] = 'This is already your e-mail address!';
} else {
if (!empty($_POST['email']['new'])) {
if (empty($_POST['email']['confirm'])
|| $_POST['email']['new'] !== $_POST['email']['confirm']) {
$settingsErrors[] = 'The given e-mail addresses did not match.';
} else {
$email_validate = user_validate_email($_POST['email']['new'], true);
$checkMail = user_validate_email($_POST['email']['new'], true);
if ($email_validate !== '') {
switch ($email_validate) {
case 'dns':
$settingsErrors[] = 'No valid MX record exists for this domain.';
break;
if ($checkMail !== '') {
switch ($checkMail) {
case 'dns':
$errors[] = 'No valid MX record exists for this domain.';
break;
case 'format':
$settingsErrors[] = 'The given e-mail address was incorrectly formatted.';
break;
case 'format':
$errors[] = 'The given e-mail address was incorrectly formatted.';
break;
case 'in-use':
$settingsErrors[] = 'This e-mail address is already in use.';
break;
case 'in-use':
$errors[] = 'This e-mail address is already in use.';
break;
default:
$settingsErrors[] = 'Unknown e-mail validation error.';
}
} else {
$updateAccountFields['email'] = mb_strtolower($_POST['email']['new']);
audit_log('PERSONAL_EMAIL_CHANGE', $settingsUserId, [
$updateAccountFields['email'],
]);
}
default:
$errors[] = 'Unknown e-mail validation error.';
}
} else {
user_email_set(user_session_current('user_id'), $_POST['email']['new']);
audit_log('PERSONAL_EMAIL_CHANGE', user_session_current('user_id'), [
$_POST['email']['new'],
]);
}
}
}
if (!empty($_POST['password']['new'])) {
if (empty($_POST['password']['confirm'])
|| $_POST['password']['new'] !== $_POST['password']['confirm']) {
$settingsErrors[] = "The given passwords did not match.";
} else {
$password_validate = user_validate_password($_POST['password']['new']);
// Changing password
if (!empty($_POST['password']['new'])) {
if (empty($_POST['password']['confirm']) || $_POST['password']['new'] !== $_POST['password']['confirm']) {
$errors[] = 'The new passwords you entered did not match each other.';
} else {
$checkPassword = user_validate_password($_POST['password']['new']);
if ($password_validate !== '') {
$settingsErrors[] = "The given passwords was too weak.";
} else {
$updateAccountFields['password'] = user_password_hash($_POST['password']['new']);
audit_log('PERSONAL_PASSWORD_CHANGE', $settingsUserId);
}
}
}
if (count($updateAccountFields) > 0) {
$updateUser = db_prepare('
UPDATE `msz_users`
SET ' . pdo_prepare_array_update($updateAccountFields, true) . '
WHERE `user_id` = :user_id
');
$updateAccountFields['user_id'] = $settingsUserId;
$updateUser->execute($updateAccountFields);
if ($checkPassword !== '') {
$errors[] = 'The given passwords was too weak.';
} else {
user_password_set(user_session_current('user_id'), $_POST['password']['new']);
audit_log('PERSONAL_PASSWORD_CHANGE', user_session_current('user_id'));
}
}
}
@ -149,92 +112,27 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
}
}
tpl_vars([
'settings_errors' => $settingsErrors,
]);
$sessions = [
'list' => [],
'active' => user_session_current('session_id'),
'amount' => user_session_count(user_session_current('user_id')),
'offset' => max(0, intval($_GET['sessions']['offset'] ?? 0)),
'take' => clamp($_GET['sessions']['take'] ?? 15, 5, 30),
];
$getAccountInfo = db_prepare(sprintf('
SELECT `email`
FROM `msz_users`
WHERE `user_id` = :user_id
'));
$getAccountInfo->bindValue('user_id', $settingsUserId);
$accountInfo = $getAccountInfo->execute() ? $getAccountInfo->fetch(PDO::FETCH_ASSOC) : [];
$logins = [
'list' => [],
'amount' => user_login_attempts_count(user_session_current('user_id')),
'offset' => max(0, intval($_GET['logins']['offset'] ?? 0)),
'take' => clamp($_GET['logins']['take'] ?? 15, 5, 30),
];
tpl_vars([
'background' => $backgroundProps,
'settings_disable_account_options' => $disableAccountOptions,
'account_info' => $accountInfo,
]);
$getSessionCount = db_prepare('
SELECT COUNT(`session_id`)
FROM `msz_sessions`
WHERE `user_id` = :user_id
');
$getSessionCount->bindValue('user_id', $settingsUserId);
$sessionCount = $getSessionCount->execute() ? $getSessionCount->fetchColumn() : 0;
$getSessions = db_prepare('
SELECT
`session_id`, `session_country`, `user_agent`, `created_at`, `expires_on`,
INET6_NTOA(`session_ip`) as `session_ip_decoded`
FROM `msz_sessions`
WHERE `user_id` = :user_id
ORDER BY `session_id` DESC
LIMIT :offset, :take
');
$getSessions->bindValue('offset', $queryOffset);
$getSessions->bindValue('take', $queryTake);
$getSessions->bindValue('user_id', $settingsUserId);
$sessions = $getSessions->execute() ? $getSessions->fetchAll() : [];
tpl_vars([
'active_session_id' => user_session_current('session_id'),
'user_sessions' => $sessions,
'sessions_offset' => $queryOffset,
'sessions_take' => $queryTake,
'sessions_count' => $sessionCount,
]);
$loginAttemptsOffset = max(0, $_GET['lo'] ?? 0);
$auditLogOffset = max(0, $_GET['ao'] ?? 0);
$getLoginAttemptsCount = db_prepare('
SELECT COUNT(`attempt_id`)
FROM `msz_login_attempts`
WHERE `user_id` = :user_id
');
$getLoginAttemptsCount->bindValue('user_id', $settingsUserId);
$loginAttemptsCount = $getLoginAttemptsCount->execute() ? $getLoginAttemptsCount->fetchColumn() : 0;
$getLoginAttempts = db_prepare('
SELECT
`attempt_id`, `attempt_country`, `was_successful`, `user_agent`, `created_at`,
INET6_NTOA(`attempt_ip`) as `attempt_ip_decoded`
FROM `msz_login_attempts`
WHERE `user_id` = :user_id
ORDER BY `attempt_id` DESC
LIMIT :offset, :take
');
$getLoginAttempts->bindValue('offset', $loginAttemptsOffset);
$getLoginAttempts->bindValue('take', min(20, max(5, $queryTake)));
$getLoginAttempts->bindValue('user_id', $settingsUserId);
$loginAttempts = $getLoginAttempts->execute() ? $getLoginAttempts->fetchAll() : [];
$auditLogCount = audit_log_count($settingsUserId);
$auditLog = audit_log_list(
$auditLogOffset,
min(20, max(5, $queryTake)),
$settingsUserId
);
tpl_vars([
'audit_logs' => $auditLog,
'audit_log_count' => $auditLogCount,
'audit_log_take' => $queryTake,
'audit_log_offset' => $auditLogOffset,
'log_strings' => [
$logs = [
'list' => [],
'amount' => audit_log_count(user_session_current('user_id')),
'offset' => max(0, intval($_GET['logs']['offset'] ?? 0)),
'take' => clamp($_GET['logs']['take'] ?? 15, 5, 30),
'strings' => [
'PERSONAL_EMAIL_CHANGE' => 'Changed e-mail address to %s.',
'PERSONAL_PASSWORD_CHANGE' => 'Changed account password.',
'PERSONAL_SESSION_DESTROY' => 'Ended session #%d.',
@ -249,10 +147,21 @@ tpl_vars([
'CHANGELOG_ACTION_CREATE' => 'Created new changelog action #%d.',
'CHANGELOG_ACTION_EDIT' => 'Edited changelog action #%d.',
],
'user_login_attempts' => $loginAttempts,
'login_attempts_offset' => $loginAttemptsOffset,
'login_attempts_take' => $queryTake,
'login_attempts_count' => $loginAttemptsCount,
]);
];
echo tpl_render('user.settings');
$sessions['list'] = user_session_list($sessions['offset'], $sessions['take'], user_session_current('user_id'));
$logins['list'] = user_login_attempts_list($sessions['offset'], $sessions['take'], user_session_current('user_id'));
$logs['list'] = audit_log_list($logs['offset'], $logs['take'], user_session_current('user_id'));
if (empty($errors)) {
$errors[] = 'A few of the elements on this page have been moved to the on-profile editor. To find them, go to your profile and hit the "Edit Profile" button below your avatar.';
}
echo tpl_render('user.settings', [
'errors' => $errors,
'disable_account_options' => $disableAccountOptions,
'current_email' => $currentEmail,
'sessions' => $sessions,
'logins' => $logins,
'logs' => $logs,
]);

View file

@ -31,3 +31,44 @@ function user_login_attempts_remaining(string $ipAddress): int
? (int)$getRemaining->fetchColumn()
: 0;
}
function user_login_attempts_count($userId = 0): int
{
$getCount = db_prepare(sprintf('
SELECT COUNT(`attempt_id`)
FROM `msz_login_attempts`
WHERE %s
', $userId < 1 ? '1' : '`user_id` = :user_id'));
if ($userId >= 1) {
$getCount->bindValue('user_id', $userId);
}
return $getCount->execute() ? (int)$getCount->fetchColumn() : 0;
}
function user_login_attempts_list(int $offset, int $take, int $userId = 0): array
{
$offset = max(0, $offset);
$take = max(1, $take);
$getAttempts = db_prepare(sprintf('
SELECT
`attempt_id`, `attempt_country`, `was_successful`, `user_agent`, `created_at`,
INET6_NTOA(`attempt_ip`) as `attempt_ip`
FROM `msz_login_attempts`
WHERE %s
ORDER BY `attempt_id` DESC
LIMIT :offset, :take
', $userId < 1 ? '1' : '`user_id` = :user_id'));
if ($userId > 0) {
$getAttempts->bindValue('user_id', $userId);
}
$getAttempts->bindValue('offset', $offset);
$getAttempts->bindValue('take', $take);
$attempts = $getAttempts->execute() ? $getAttempts->fetchAll(PDO::FETCH_ASSOC) : false;
return $attempts ? $attempts : [];
}

View file

@ -73,6 +73,47 @@ function user_session_purge_all(int $userId): void
]);
}
function user_session_count($userId = 0): int
{
$getCount = db_prepare(sprintf('
SELECT COUNT(`session_id`)
FROM `msz_sessions`
WHERE %s
', $userId < 1 ? '1' : '`user_id` = :user_id'));
if ($userId >= 1) {
$getCount->bindValue('user_id', $userId);
}
return $getCount->execute() ? (int)$getCount->fetchColumn() : 0;
}
function user_session_list(int $offset, int $take, int $userId = 0): array
{
$offset = max(0, $offset);
$take = max(1, $take);
$getSessions = db_prepare(sprintf('
SELECT
`session_id`, `session_country`, `user_agent`, `created_at`, `expires_on`,
INET6_NTOA(`session_ip`) as `session_ip`
FROM `msz_sessions`
WHERE %s
ORDER BY `session_id` DESC
LIMIT :offset, :take
', $userId < 1 ? '1' : '`user_id` = :user_id'));
if ($userId > 0) {
$getSessions->bindValue('user_id', $userId);
}
$getSessions->bindValue('offset', $offset);
$getSessions->bindValue('take', $take);
$sessions = $getSessions->execute() ? $getSessions->fetchAll(PDO::FETCH_ASSOC) : false;
return $sessions ? $sessions : [];
}
// the functions below this line are imperative
function user_session_start(int $userId, string $sessionKey): bool

View file

@ -67,6 +67,50 @@ function user_password_set(int $userId, string $password): bool
return $updatePassword->execute();
}
function user_email_get(int $userId): string
{
if ($userId < 1) {
return '';
}
$fetchMail = db_prepare('
SELECT `email`
FROM `msz_users`
WHERE `user_id` = :user_id
');
$fetchMail->bindValue('user_id', $userId);
return $fetchMail->execute() ? (string)$fetchMail->fetchColumn() : '';
}
function user_email_set(int $userId, string $email): bool
{
$updateMail = db_prepare('
UPDATE `msz_users`
SET `email` = LOWER(:email)
WHERE `user_id` = :user
');
$updateMail->bindValue('user', $userId);
$updateMail->bindValue('email', $email);
return $updateMail->execute();
}
function user_password_verify_db(int $userId, string $password): bool
{
if ($userId < 1) {
return false;
}
$fetchPassword = db_prepare('
SELECT `password`
FROM `msz_users`
WHERE `user_id` = :user_id
');
$fetchPassword->bindValue('user_id', $userId);
$currentPassword = $fetchPassword->execute() ? $fetchPassword->fetchColumn() : '';
return !empty($currentPassword) && password_verify($password, $currentPassword);
}
// function of the century, only use this if it doesn't make sense to grab data otherwise
function user_exists(int $userId): bool
{

View file

@ -69,6 +69,7 @@ function audit_log_list(int $offset, int $take, int $userId = 0): array
$getLogs->bindValue('offset', $offset);
$getLogs->bindValue('take', $take);
$logs = $getLogs->execute() ? $getLogs->fetchAll(PDO::FETCH_ASSOC) : false;
return $getLogs->execute() ? $getLogs->fetchAll(PDO::FETCH_ASSOC) : [];
return $logs ? $logs : [];
}

View file

@ -76,7 +76,7 @@
IP Address
</div>
<div class="settings__session__detail__value">
{{ session.session_ip_decoded }}
{{ session.session_ip }}
</div>
</div>

View file

@ -6,20 +6,14 @@
{% set title = 'Settings' %}
{% block content %}
{% if settings_errors is defined and settings_errors|length > 0 %}
{% if errors|length > 0 %}
<div class="warning">
<div class="warning__content">
{% for error in settings_errors %}
{% for error in errors %}
{{ error }}
{% endfor %}
</div>
</div>
{% else %}
<div class="warning">
<div class="warning__content">
A few of the elements on this page have been moved to the on-profile editor. To find them, go to your profile and hit the "Edit Profile" button below your avatar.
</div>
</div>
{% endif %}
<form action="" method="post" class="container settings__container" id="account">
@ -30,7 +24,7 @@
<p>Here you can change your e-mail address and/or your password, please make sure your e-mail is accurate and your password is strong in order to protect your account. For convenience your current e-mail address is displayed. You are required to verify yourself by entering your current password to change either value.</p>
</div>
{% if settings_disable_account_options %}
{% if disable_account_options %}
<div class="settings__disabled">
<div class="settings__disabled__notice">
E-mail and password changing is only available on the main site for stability reasons.
@ -48,14 +42,14 @@
<div class="settings__account__title">
New e-mail address
</div>
{{ input_text('email[new]', 'settings__account__input', '', 'email', account_info.email) }}
{{ input_text('email[new]', 'settings__account__input', '', 'email', current_email) }}
</label>
<label class="settings__account__input">
<div class="settings__account__title">
Confirm new e-mail address
</div>
{{ input_text('email[new]', 'settings__account__input', '', 'email') }}
{{ input_text('email[confirm]', 'settings__account__input', '', 'email') }}
</label>
</div>
@ -94,7 +88,20 @@
<div class="container settings__container" id="sessions">
{{ container_title('<i class="fas fa-key fa-fw"></i> Sessions', '', true) }}
{% set spagination = pagination(sessions_count, sessions_take, sessions_offset, '?m=sessions') %}
{% set spagination = pagination(
sessions.amount,
sessions.take,
sessions.offset,
''|url_construct({
'logins[offset]': logins.offset,
'logins[take]': logins.take == 15 ? 0 : logins.take,
'logs[offset]': logs.offset,
'logs[take]': logs.take == 15 ? 0 : logs.take,
'sessions[take]': sessions.take == 15 ? 0 : sessions.take,
}),
false,
'sessions[offset]'
) %}
<div class="settings__description">
<p>These are the active logins to your account, clicking the Kill button will force a logout on that session. Your current login is highlighted with a different colour so you don't accidentally force yourself to logout.</p>
@ -115,8 +122,8 @@
</div>
<div class="settings__sessions__list">
{% for session in user_sessions %}
{{ user_session(session, session.session_id == active_session_id) }}
{% for session in sessions.list %}
{{ user_session(session, session.session_id == sessions.active) }}
{% endfor %}
</div>
@ -129,12 +136,18 @@
<div class="container settings__container" id="logins">
{{ container_title('<i class="fas fa-user-lock fa-fw"></i> Login History', '', true) }}
{% set lhpagination = pagination(
login_attempts_count,
login_attempts_take,
login_attempts_offset,
'?m=logs'|url_construct({'ao': audit_log_offset}),
logins.amount,
logins.take,
logins.offset,
''|url_construct({
'logins[take]': logins.take == 15 ? 0 : logins.take,
'logs[offset]': logs.offset,
'logs[take]': logs.take == 15 ? 0 : logs.take,
'sessions[offset]': sessions.offset,
'sessions[take]': sessions.take == 15 ? 0 : sessions.take,
}),
false,
'lo'
'logins[offset]'
) %}
<div class="settings__description">
@ -144,14 +157,14 @@
<div class="settings__login-history">
{{ lhpagination }}
{% for attempt in user_login_attempts %}
{% for attempt in logins.list %}
<div class="settings__login-history__entry" id="attempt-{{ attempt.attempt_id }}">
<div class="settings__login-history__column settings__login-history__column--ip">
<div class="settings__login-history__column__name">
IP
</div>
<div class="settings__login-history__column__value">
{{ attempt.attempt_ip_decoded }}
{{ attempt.attempt_ip }}
{% if attempt.attempt_country != 'XX' %}
<div class="flag flag--{{ attempt.attempt_country|lower }} settings__login-history__country" title="{{ attempt.attempt_country|country_name }}"></div>
{% endif %}
@ -196,12 +209,18 @@
<div class="container settings__container" id="log">
{{ container_title('<i class="fas fa-file-alt fa-fw"></i> Account Log', '', true) }}
{% set alpagination = pagination(
audit_log_count,
audit_log_take,
audit_log_offset,
'?m=logs'|url_construct({'lo': login_attempts_offset}),
logs.amount,
logs.take,
logs.offset,
''|url_construct({
'logins[offset]': logins.offset,
'logins[take]': logins.take == 15 ? 0 : logins.take,
'logs[take]': logs.take == 15 ? 0 : logs.take,
'sessions[offset]': sessions.offset,
'sessions[take]': sessions.take == 15 ? 0 : sessions.take,
}),
false,
'ao'
'logs[offset]'
) %}
<div class="settings__description">
@ -211,7 +230,7 @@
<div class="settings__log">
{{ alpagination }}
{% for log in audit_logs %}
{% for log in logs.list %}
<div class="settings__log__entry" id="log-{{ log.log_id }}">
<div class="settings__log__column settings__login-history__column--ip">
<div class="settings__log__column__name">
@ -239,8 +258,8 @@
Action
</div>
<div class="settings__log__column__value">
{% if log.log_action in log_strings|keys %}
{{ log_strings[log.log_action]|vsprintf(log.log_params|json_decode) }}
{% if log.log_action in logs.strings|keys %}
{{ logs.strings[log.log_action]|vsprintf(log.log_params|json_decode) }}
{% else %}
{{ log.log_action }}({{ log.log_params }})
{% endif %}

View file

@ -12,6 +12,11 @@ function set_cookie_m(string $name, string $value, int $expires): void
);
}
function clamp($num, int $min, int $max): int
{
return max($min, min($max, intval($num)));
}
function password_entropy(string $password): int
{
return count(count_chars(utf8_decode($password), 1)) * 8;