diff --git a/assets/less/settings/data.less b/assets/less/settings/data.less new file mode 100644 index 00000000..b591a8fe --- /dev/null +++ b/assets/less/settings/data.less @@ -0,0 +1,16 @@ +.settings__data { + + &__content { + padding: 5px; + } + + &__password { + display: block; + width: 100%; + } + + &__actions { + margin-top: 5px; + text-align: center; + } +} diff --git a/assets/less/settings/login-attempts.less b/assets/less/settings/login-attempts.less index c3c15d48..36e6440b 100644 --- a/assets/less/settings/login-attempts.less +++ b/assets/less/settings/login-attempts.less @@ -3,4 +3,9 @@ &__pagination { margin: 4px; } + + &__none { + padding: 2px 5px; + text-align: center; + } } diff --git a/assets/less/settings/settings.less b/assets/less/settings/settings.less index 1ed69eab..0979c501 100644 --- a/assets/less/settings/settings.less +++ b/assets/less/settings/settings.less @@ -2,6 +2,7 @@ @import "account-logs"; @import "account"; @import "container"; +@import "data"; @import "description"; @import "login-attempt"; @import "login-attempts"; diff --git a/assets/less/settings/wrapper.less b/assets/less/settings/wrapper.less index b5271233..9ebfdffa 100644 --- a/assets/less/settings/wrapper.less +++ b/assets/less/settings/wrapper.less @@ -13,6 +13,10 @@ } } + &__content { + flex: 1 1 auto; + } + &__menu { width: 280px; margin-right: 2px; diff --git a/public/settings/data.php b/public/settings/data.php new file mode 100644 index 00000000..3b9bd0f6 --- /dev/null +++ b/public/settings/data.php @@ -0,0 +1,81 @@ +bindValue('user_id', $userId); + } else { + for($i = 1; $i <= $params; $i++) { + $prepare->bindValue('user_id_' . $i, $userId); + } + } + + $archive->addFromString($filename, json_encode(db_fetch_all($prepare), JSON_PRETTY_PRINT)); +} + +$errors = []; +$currentUserId = user_session_current('user_id'); + +if(isset($_POST['action']) && is_string($_POST['action'])) { + if(isset($_POST['password']) && is_string($_POST['password']) + && user_password_verify_db($currentUserId, $_POST['password'])) { + switch($_POST['action']) { + case 'data': + audit_log(MSZ_AUDIT_PERSONAL_DATA_DOWNLOAD, $currentUserId); + + $filename = tempnam(sys_get_temp_dir(), 'msz'); + $archive = new ZipArchive; + $archive->open($filename, ZipArchive::CREATE); + + db_to_zip($archive, $currentUserId, 'audit_log.json', 'SELECT *, INET6_NTOA(`log_ip`) AS `log_ip` FROM `msz_audit_log` WHERE `user_id` = :user_id'); + db_to_zip($archive, $currentUserId, 'auth_tfa.json', 'SELECT * FROM `msz_auth_tfa` WHERE `user_id` = :user_id'); + db_to_zip($archive, $currentUserId, 'changelog_changes.json', 'SELECT * FROM `msz_changelog_changes` WHERE `user_id` = :user_id'); + db_to_zip($archive, $currentUserId, 'comments_posts.json', 'SELECT * FROM `msz_comments_posts` WHERE `user_id` = :user_id'); + db_to_zip($archive, $currentUserId, 'comments_votes.json', 'SELECT * FROM `msz_comments_votes` WHERE `user_id` = :user_id'); + db_to_zip($archive, $currentUserId, 'forum_permissions.json', 'SELECT * FROM `msz_forum_permissions` WHERE `user_id` = :user_id'); + db_to_zip($archive, $currentUserId, 'forum_polls_answers.json', 'SELECT * FROM `msz_forum_polls_answers` WHERE `user_id` = :user_id'); + db_to_zip($archive, $currentUserId, 'forum_posts.json', 'SELECT *, INET6_NTOA(`post_ip`) AS `post_ip` FROM `msz_forum_posts` WHERE `user_id` = :user_id'); + db_to_zip($archive, $currentUserId, 'forum_posts_reactions.json', 'SELECT * FROM `msz_forum_posts_reactions` WHERE `user_id` = :user_id'); + db_to_zip($archive, $currentUserId, 'forum_topics.json', 'SELECT * FROM `msz_forum_topics` WHERE `user_id` = :user_id'); + db_to_zip($archive, $currentUserId, 'forum_topics_priority.json', 'SELECT * FROM `msz_forum_topics_priority` WHERE `user_id` = :user_id'); + db_to_zip($archive, $currentUserId, 'forum_topics_track.json', 'SELECT * FROM `msz_forum_topics_track` WHERE `user_id` = :user_id'); + db_to_zip($archive, $currentUserId, 'login_attempts.json', 'SELECT *, INET6_NTOA(`attempt_ip`) AS `attempt_ip` FROM `msz_login_attempts` WHERE `user_id` = :user_id'); + db_to_zip($archive, $currentUserId, 'news_posts.json', 'SELECT * FROM `msz_news_posts` WHERE `user_id` = :user_id'); + db_to_zip($archive, $currentUserId, 'permissions.json', 'SELECT * FROM `msz_permissions` WHERE `user_id` = :user_id'); + db_to_zip($archive, $currentUserId, 'sessions.json', 'SELECT *, INET6_NTOA(`session_ip`) AS `session_ip`, INET6_NTOA(`session_ip_last`) AS `session_ip_last` FROM `msz_sessions` WHERE `user_id` = :user_id'); + db_to_zip($archive, $currentUserId, 'users.json', 'SELECT *, NULL AS `password`, NULL AS `user_totp_key`, INET6_NTOA(`register_ip`) AS `register_ip`, INET6_NTOA(`last_ip`) AS `last_ip` FROM `msz_users` WHERE `user_id` = :user_id'); + db_to_zip($archive, $currentUserId, 'users_password_resets.json', 'SELECT *, INET6_NTOA(`reset_ip`) AS `reset_ip` FROM `msz_users_password_resets` WHERE `user_id` = :user_id'); + db_to_zip($archive, $currentUserId, 'user_relations.json', 'SELECT * FROM `msz_user_relations` WHERE `user_id` = :user_id_1 OR `subject_id` = :user_id_2', 2); + db_to_zip($archive, $currentUserId, 'user_roles.json', 'SELECT * FROM `msz_user_roles` WHERE `user_id` = :user_id'); + db_to_zip($archive, $currentUserId, 'user_warnings.json', 'SELECT *, INET6_NTOA(`user_ip`) AS `user_ip`, NULL AS `issuer_id`, NULL AS `issuer_ip`, NULL AS `warning_note_private` FROM `msz_user_warnings` WHERE `user_id` = :user_id'); + + $archive->close(); + + header('Content-Type: application/zip'); + header(sprintf( + 'Content-Disposition: inline; filename="misuzu-user-data-%d-%d.zip"', + $currentUserId, + time() + )); + echo file_get_contents($filename); + return; + + case 'deactivate': + // deactivation + break; + } + } else { + $errors[] = 'Incorrect password.'; + } +} + +echo tpl_render('settings.data', [ + 'errors' => $errors, +]); diff --git a/src/audit_log.php b/src/audit_log.php index ec7f6916..464d3094 100644 --- a/src/audit_log.php +++ b/src/audit_log.php @@ -3,6 +3,7 @@ audit_log_define('PERSONAL_EMAIL_CHANGE'); audit_log_define('PERSONAL_PASSWORD_CHANGE'); audit_log_define('PERSONAL_SESSION_DESTROY'); audit_log_define('PERSONAL_SESSION_DESTROY_ALL'); +audit_log_define('PERSONAL_DATA_DOWNLOAD'); audit_log_define('PASSWORD_RESET'); audit_log_define('CHANGELOG_ENTRY_CREATE'); audit_log_define('CHANGELOG_ENTRY_EDIT'); @@ -62,6 +63,7 @@ define('MSZ_AUDIT_LOG_STRINGS', [ MSZ_AUDIT_FORUM_TOPIC_BUMP => 'Manually bumped forum topic #%d.', MSZ_AUDIT_FORUM_TOPIC_LOCK => 'Locked forum topic #%d.', MSZ_AUDIT_FORUM_TOPIC_UNLOCK => 'Unlocked forum topic #%d.', + MSZ_AUDIT_PERSONAL_DATA_DOWNLOAD => 'Downloaded archive of account data.', ]); function audit_log_define(string $name): void { diff --git a/src/url.php b/src/url.php index 7185aa52..3fae5e13 100644 --- a/src/url.php +++ b/src/url.php @@ -84,6 +84,7 @@ define('MSZ_URLS', [ 'settings-account' => ['/settings/account.php'], 'settings-sessions' => ['/settings/sessions.php'], 'settings-logs' => ['/settings/logs.php'], + 'settings-data' => ['/settings/data.php'], 'comment-create' => ['/comments.php', ['m' => 'create']], 'comment-vote' => ['/comments.php', ['c' => '', 'csrf' => '{csrf}', 'm' => 'vote', 'v' => '']], diff --git a/templates/settings/data.twig b/templates/settings/data.twig new file mode 100644 index 00000000..3a32e6bb --- /dev/null +++ b/templates/settings/data.twig @@ -0,0 +1,42 @@ +{% extends 'settings/master.twig' %} +{% from 'macros.twig' import container_title %} +{% from '_layout/input.twig' import input_hidden, input_csrf, input_text, input_select %} + +{% set title = 'Settings / Data' %} + +{% block settings_content %} +
+ {{ container_title(' Download account data') }} + {{ input_csrf() }} + +
+

Here you can request raw json files containing pretty much all data relating to your account. Moderator identities are concealed and password hashes are removed from the output.

+
+ +
+ {{ input_text('password', 'settings__data__password', '', 'password', 'Password', true) }} + +
+ +
+
+
+ + {#
+ {{ container_title(' Deactivate account') }} + {{ input_csrf() }} + +
+

Deactivation will mark your account for deletion after 7 days unless you log in again. All content associated with your account EXCEPT forum topics and posts will be irrecoverably removed after these 7 days. Forum topics and posts will become associated with a default user effectively removing your identity from them. If you wish to have your forum posts removed please contact staff.

+

Temporarily deactivating as a means to gain attention of some kind is frowned upon and you will likely be banned after returning. Use this feature cautiously.

+
+ +
+ {{ input_text('password', 'settings__data__password', '', 'password', 'Password', true) }} + +
+ +
+
+
#} +{% endblock %} diff --git a/templates/settings/logs.twig b/templates/settings/logs.twig index 577d2755..6abb623f 100644 --- a/templates/settings/logs.twig +++ b/templates/settings/logs.twig @@ -20,9 +20,15 @@ {{ lhpagination }} - {% for attempt in login_history_list %} - {{ user_login_attempt(attempt) }} - {% endfor %} + {% if login_history_list|length < 1 %} + + {% else %} + {% for attempt in login_history_list %} + {{ user_login_attempt(attempt) }} + {% endfor %} + {% endif %}