diff --git a/assets/less/mio/classes/settings/log.less b/assets/less/mio/classes/settings/log.less new file mode 100644 index 00000000..80a6f4b0 --- /dev/null +++ b/assets/less/mio/classes/settings/log.less @@ -0,0 +1,49 @@ +.settings__log { + &__country { + vertical-align: middle; + } + + &__entry { + display: flex; + border: 1px solid #9475b2; + justify-content: space-between; + padding: 1px; + flex-wrap: wrap; + + &:not(:last-child) { + margin-bottom: 1px; + } + } + + &__column { + flex-grow: 1; + margin-left: 5px; + margin-right: 1px; + + &--ip, + &--date { + flex-shrink: 0; + + &:not(:last-child) { + flex-grow: 0; + } + } + + &--action { + flex-shrink: 1; + flex-grow: 1; + } + + &--ip { + min-width: 200px; + } + + &--date { + min-width: 120px; + } + + &__name { + font-weight: 700; + } + } +} diff --git a/assets/less/mio/main.less b/assets/less/mio/main.less index dd42f753..3eb23a85 100644 --- a/assets/less/mio/main.less +++ b/assets/less/mio/main.less @@ -69,6 +69,7 @@ body { @import "classes/settings/account"; @import "classes/settings/images"; @import "classes/settings/avatar"; +@import "classes/settings/log"; @import "classes/settings/login-history"; @import "classes/settings/sessions"; diff --git a/database/2018_07_23_131728_add_country_to_audit_log.php b/database/2018_07_23_131728_add_country_to_audit_log.php new file mode 100644 index 00000000..74afe39f --- /dev/null +++ b/database/2018_07_23_131728_add_country_to_audit_log.php @@ -0,0 +1,20 @@ +exec(' + ALTER TABLE `msz_audit_log` + ADD COLUMN `log_country` CHAR(2) NOT NULL DEFAULT \'XX\' AFTER `log_ip`; + '); +} + +function migrate_down(PDO $conn): void +{ + $conn->exec(' + ALTER TABLE `msz_audit_log` + DROP COLUMN `log_country`; + '); +} diff --git a/public/settings.php b/public/settings.php index e4828aac..cb03fe87 100644 --- a/public/settings.php +++ b/public/settings.php @@ -28,6 +28,10 @@ $settingsModes = [ 'title' => 'Login History', 'allow' => true, ], + 'log' => [ + 'title' => 'Account Log', + 'allow' => true, + ], ]; $settingsMode = $_GET['m'] ?? null; @@ -426,6 +430,36 @@ switch ($settingsMode) { 'login_attempts_count' => $loginAttemptsCount, ]); break; + + case 'log': + $auditLogCount = audit_log_count($app->getUserId()); + $auditLog = audit_log_list( + $queryOffset, + max(20, $queryTake), + $app->getUserId() + ); + + $tpl->vars([ + 'audit_logs' => $auditLog, + 'audit_log_count' => $auditLogCount, + 'audit_log_take' => $queryTake, + 'audit_log_offset' => $queryOffset, + 'log_strings' => [ + 'PERSONAL_EMAIL_CHANGE' => 'Changed e-mail address to %s.', + 'PERSONAL_PASSWORD_CHANGE' => 'Changed account password.', + 'PERSONAL_SESSION_DESTROY' => 'Ended session #%d.', + 'PASSWORD_RESET' => 'Successfully used the password reset form to change password.', + 'CHANGELOG_ENTRY_CREATE' => 'Created a new changelog entry #%d.', + 'CHANGELOG_ENTRY_EDIT' => 'Edited changelog entry #%d.', + 'CHANGELOG_TAG_ADD' => 'Added tag #%2$d to changelog entry #%1$d.', + 'CHANGELOG_TAG_REMOVE' => 'Removed tag #%2$d from changelog entry #%1$d.', + 'CHANGELOG_TAG_CREATE' => 'Created new changelog tag #%d.', + 'CHANGELOG_TAG_EDIT' => 'Edited changelog tag #%d.', + 'CHANGELOG_ACTION_CREATE' => 'Created new changelog action #%d.', + 'CHANGELOG_ACTION_EDITl' => 'Edited changelog action #%d.', + ], + ]); + break; } echo $tpl->render("settings.{$settingsMode}"); diff --git a/src/Application.php b/src/Application.php index 3dbfc896..7a6aa8c1 100644 --- a/src/Application.php +++ b/src/Application.php @@ -296,6 +296,7 @@ class Application extends ApplicationBase $this->templatingInstance->addFilter('parse_line'); $this->templatingInstance->addFilter('parse_text'); $this->templatingInstance->addFilter('asset_url'); + $this->templatingInstance->addFilter('vsprintf'); $this->templatingInstance->addFunction('git_commit_hash'); $this->templatingInstance->addFunction('git_branch'); diff --git a/src/audit_log.php b/src/audit_log.php index 0401a63f..9a7974a4 100644 --- a/src/audit_log.php +++ b/src/audit_log.php @@ -11,24 +11,40 @@ function audit_log( $ipAddress = $ipAddress ?? IPAddress::remote(); for ($i = 0; $i < count($params); $i++) { - if (preg_match('#(-?[0-9]+)#', $params[$i])) { + if (preg_match('#^(-?[0-9]+)$#', $params[$i])) { $params[$i] = (int)$params[$i]; } } $addLog = Database::prepare(' INSERT INTO `msz_audit_log` - (`log_action`, `user_id`, `log_params`, `log_ip`) + (`log_action`, `user_id`, `log_params`, `log_ip`, `log_country`) VALUES - (:action, :user, :params, :log_ip) + (:action, :user, :params, :ip, :country) '); $addLog->bindValue('action', $action); $addLog->bindValue('user', $userId < 1 ? null : $userId); $addLog->bindValue('params', json_encode($params)); - $addLog->bindValue('log_ip', $ipAddress->getRaw()); + $addLog->bindValue('ip', $ipAddress->getRaw()); + $addLog->bindValue('country', $ipAddress->getCountryCode()); $addLog->execute(); } +function audit_log_count($userId = 0): int +{ + $getCount = Database::prepare(sprintf(' + SELECT COUNT(`log_id`) + FROM `msz_audit_log` + 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 audit_log_list(int $offset, int $take, int $userId = 0): array { $offset = max(0, $offset); @@ -36,7 +52,7 @@ function audit_log_list(int $offset, int $take, int $userId = 0): array $getLogs = Database::prepare(sprintf(' SELECT - l.`log_id`, l.`log_action`, l.`log_params`, l.`log_created`, + l.`log_id`, l.`log_action`, l.`log_params`, l.`log_created`, l.`log_country`, u.`user_id`, u.`username`, INET6_NTOA(l.`log_ip`) as `log_ip`, COALESCE(u.`user_colour`, r.`role_colour`) as `user_colour` @@ -51,7 +67,7 @@ function audit_log_list(int $offset, int $take, int $userId = 0): array ', $userId < 1 ? '1' : 'l.`user_id` = :user_id')); if ($userId >= 1) { - $getLogs->bindValue('user_id'); + $getLogs->bindValue('user_id', $userId); } $getLogs->bindValue('offset', $offset); diff --git a/views/mio/settings/log.twig b/views/mio/settings/log.twig new file mode 100644 index 00000000..3fc61234 --- /dev/null +++ b/views/mio/settings/log.twig @@ -0,0 +1,54 @@ +{% extends '@mio/settings/master.twig' %} +{% from '@mio/macros.twig' import pagination %} + +{% set alpagination = pagination(audit_log_count, audit_log_take, audit_log_offset, '?m=log', 'settings__') %} + +{% block settings_content %} +
+

This is a log of all "important" actions that have been done using your account for your review. If you notice anything strange, please alert the staff.

+
+ +
+ {{ alpagination }} + + {% for log in audit_logs %} +
+ + +
+
+ Date +
+ +
+ +
+
+ Action +
+
+ {% if log.log_action in log_strings|keys %} + {{ log_strings[log.log_action]|vsprintf(log.log_params|json_decode) }} + {% else %} + {{ log.log_action }}({{ log.log_params }}) + {% endif %} +
+
+
+ {% endfor %} + + {{ alpagination }} +
+{% endblock %}