From 67d59d90bf8ace1987dbf87308b459eba3329a5a Mon Sep 17 00:00:00 2001 From: flashwave Date: Mon, 29 Oct 2018 20:12:06 +0100 Subject: [PATCH] Massively cleaned up settings.php. --- public/settings.php | 265 ++++++++++++----------------------- src/Users/login_attempt.php | 41 ++++++ src/Users/session.php | 41 ++++++ src/Users/user.php | 44 ++++++ src/audit_log.php | 3 +- templates/user/macros.twig | 2 +- templates/user/settings.twig | 77 ++++++---- utility.php | 5 + 8 files changed, 269 insertions(+), 209 deletions(-) diff --git a/public/settings.php b/public/settings.php index fbdd1719..475bce93 100644 --- a/public/settings.php +++ b/public/settings.php @@ -1,33 +1,23 @@ $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, +]); diff --git a/src/Users/login_attempt.php b/src/Users/login_attempt.php index 04044f1a..904618ee 100644 --- a/src/Users/login_attempt.php +++ b/src/Users/login_attempt.php @@ -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 : []; +} diff --git a/src/Users/session.php b/src/Users/session.php index 6985f46d..d7d0cfa4 100644 --- a/src/Users/session.php +++ b/src/Users/session.php @@ -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 diff --git a/src/Users/user.php b/src/Users/user.php index 62377649..9e973368 100644 --- a/src/Users/user.php +++ b/src/Users/user.php @@ -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 { diff --git a/src/audit_log.php b/src/audit_log.php index be6b3eb1..10220550 100644 --- a/src/audit_log.php +++ b/src/audit_log.php @@ -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 : []; } diff --git a/templates/user/macros.twig b/templates/user/macros.twig index 286f32d1..a38eb267 100644 --- a/templates/user/macros.twig +++ b/templates/user/macros.twig @@ -76,7 +76,7 @@ IP Address
- {{ session.session_ip_decoded }} + {{ session.session_ip }}
diff --git a/templates/user/settings.twig b/templates/user/settings.twig index 1fdeb72c..540b5ab4 100644 --- a/templates/user/settings.twig +++ b/templates/user/settings.twig @@ -6,20 +6,14 @@ {% set title = 'Settings' %} {% block content %} - {% if settings_errors is defined and settings_errors|length > 0 %} + {% if errors|length > 0 %}
- {% for error in settings_errors %} + {% for error in errors %} {{ error }} {% endfor %}
- {% else %} -
-
- 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. -
-
{% endif %}
@@ -30,7 +24,7 @@

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.

- {% if settings_disable_account_options %} + {% if disable_account_options %}
E-mail and password changing is only available on the main site for stability reasons. @@ -48,14 +42,14 @@ - {{ input_text('email[new]', 'settings__account__input', '', 'email', account_info.email) }} + {{ input_text('email[new]', 'settings__account__input', '', 'email', current_email) }}
@@ -94,7 +88,20 @@
{{ container_title(' 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]' + ) %}

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.

@@ -115,8 +122,8 @@
- {% 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 %}
@@ -129,12 +136,18 @@
{{ container_title(' 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]' ) %}
@@ -144,14 +157,14 @@