From 01454544896827113e49db0d2848b5aab6ce14ae Mon Sep 17 00:00:00 2001 From: Jonas Kohl Date: Wed, 18 Sep 2024 23:05:35 +0200 Subject: Many changes --- src/index.php | 261 ++++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 171 insertions(+), 90 deletions(-) (limited to 'src/index.php') diff --git a/src/index.php b/src/index.php index f3a59b2..801cc07 100644 --- a/src/index.php +++ b/src/index.php @@ -18,6 +18,7 @@ use mystic\forum\utils\ValidationUtils; use Symfony\Component\Mailer\Transport; use Symfony\Component\Mime\Address; use Symfony\Component\Mime\Email; +use Symfony\Contracts\Service\Attribute\Required; header_remove("X-Powered-By"); @@ -134,6 +135,9 @@ function reArrayFiles(&$file_post) { } function renderPost(string $contents): string { + $contents = preg_replace('~\R~u', "\n", $contents); + $contents = trim($contents); + $contents = preg_replace('/\n{3,}/', "\n\n", $contents); $contents = htmlentities($contents); $contents = nl2br($contents, false); $lines = explode("\n", $contents); @@ -220,8 +224,15 @@ $superuser->id = "SUPERUSER"; if (!$db->fetch($superuser)) { $superUserPassword = base64_encode(random_bytes(12)); + $suEmail = env("MYSTIC_FORUM_SUPERUSER_EMAIL"); + if ($suEmail === null) { + http_response_code(500); + Messaging::error("No superuser email defined. Please set the 'MYSTIC_FORUM_SUPERUSER_EMAIL' environment variable accordingly"); + exit; + } + $superuser->name = "superuser"; - $superuser->email = ""; + $superuser->email = $suEmail; $superuser->passwordHash = password_hash($superUserPassword, PASSWORD_DEFAULT); $superuser->displayName = "SuperUser"; $superuser->created = new \DateTimeImmutable(); @@ -255,17 +266,18 @@ if ($_action === "auth") { } if (RequestUtils::isRequestMethod("POST")) { - $username = RequestUtils::getRequiredField("username"); - $password = RequestUtils::getRequiredField("password"); + $formId = "login"; + $username = RequestUtils::getRequiredField("username", $formId); + $password = RequestUtils::getRequiredField("password", $formId); $user = new User(); $user->name = $username; if (!$db->fetchWhere($user, "name") || !password_verify($password, $user->passwordHash)) { - RequestUtils::triggerFormError(__("Username or password incorrect!")); + RequestUtils::triggerFormError(__("Username or password incorrect!"), $formId); } if (!$user->activated) { - RequestUtils::triggerFormError(__("Please activate your user account first!")); + RequestUtils::triggerFormError(__("Please activate your user account first!"), $formId); } RequestUtils::setAuthorizedUser($user); @@ -291,40 +303,41 @@ if ($_action === "auth") { } if (RequestUtils::isRequestMethod("POST")) { + $formId = "register"; $doNotFill = $_POST["username"] ?? null; if (!empty($doNotFill)) { sleep(10); http_response_code(204); exit; } - $username = RequestUtils::getRequiredField("df82a9bc21"); - $password = RequestUtils::getRequiredField("password"); - $passwordRetype = RequestUtils::getRequiredField("password_retype"); - $email = trim(RequestUtils::getRequiredField("email")); - $displayName = RequestUtils::getRequiredField("display_name"); - $captcha = RequestUtils::getRequiredField("captcha"); + $username = RequestUtils::getRequiredField("df82a9bc21", $formId); + $password = RequestUtils::getRequiredField("password", $formId); + $passwordRetype = RequestUtils::getRequiredField("password_retype", $formId); + $email = trim(RequestUtils::getRequiredField("email", $formId)); + $displayName = RequestUtils::getRequiredField("display_name", $formId); + $captcha = RequestUtils::getRequiredField("captcha", $formId); if ($captcha !== ($_SESSION["captchaPhrase"] ?? null)) { - RequestUtils::triggerFormError(__("Incorrect CAPTCHA text!")); + RequestUtils::triggerFormError(__("Incorrect CAPTCHA text!"), $formId); } // usernames are always lowercase $username = strtolower($username); if ($password !== $passwordRetype) { - RequestUtils::triggerFormError(__("Passwords do not match!")); + RequestUtils::triggerFormError(__("Passwords do not match!"), $formId); } if (strlen($password) < 8) { - RequestUtils::triggerFormError(__("Password too short! Your password must consist of 8 or more characters")); + RequestUtils::triggerFormError(__("Password too short! Your password must consist of 8 or more characters"), $formId); } if (!ValidationUtils::isUsernameValid($username)) { - RequestUtils::triggerFormError(__("Username has an invalid format")); + RequestUtils::triggerFormError(__("Username has an invalid format"), $formId); } if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) { - RequestUtils::triggerFormError(__("Invalid email address")); + RequestUtils::triggerFormError(__("Invalid email address"), $formId); } $user = new User(); @@ -332,11 +345,11 @@ if ($_action === "auth") { $user->email = $email; if ($db->fetchWhere($user, "name")) { - RequestUtils::triggerFormError(__("This username is already taken!")); + RequestUtils::triggerFormError(__("This username is already taken!"), $formId); } if ($db->fetchWhere($user, "email")) { - RequestUtils::triggerFormError(__("This email address is already in use!")); + RequestUtils::triggerFormError(__("This email address is already in use!"), $formId); } // re-create user so we don't forget to clear properties set by the above queries @@ -429,6 +442,7 @@ if ($_action === "auth") { RequestUtils::unsetAuthorizedUser(); header("Location: " . ($_GET["next"] ?? ".")); } elseif ($_action === "viewtopic") { + $formId = "addpost"; $topicId = $_GET["topic"] ?? throw new Exception("Missing topic id"); $topic = new Topic(); $topic->id = $topicId; @@ -448,19 +462,19 @@ if ($_action === "auth") { $attachments = reArrayFiles($_FILES["files"]); if (count($attachments) > MAX_ATTACHMENT_COUNT) - RequestUtils::triggerFormError(__("Too many attachments")); + RequestUtils::triggerFormError(__("Too many attachments"), $formId); // check all attachments before saving one foreach ($attachments as $att) { if ($att["size"] > MAX_ATTACHMENT_SIZE) { - RequestUtils::triggerFormError(__("Individual file size exceeded")); + RequestUtils::triggerFormError(__("Individual file size exceeded"), $formId); } } - $message = trim(RequestUtils::getRequiredField("message")); + $message = trim(RequestUtils::getRequiredField("message", $formId)); if (strlen($message) < 1 || strlen($message) > 0x8000) { - RequestUtils::triggerFormError(__("Message too short or too long!")); + RequestUtils::triggerFormError(__("Message too short or too long!"), $formId); } $post = new Post(); @@ -552,27 +566,28 @@ if ($_action === "auth") { } if (RequestUtils::isRequestMethod("POST")) { - $title = trim(RequestUtils::getRequiredField("title")); - $message = trim(RequestUtils::getRequiredField("message")); + $formId = "newtopic"; + $title = trim(RequestUtils::getRequiredField("title", $formId)); + $message = trim(RequestUtils::getRequiredField("message", $formId)); $attachments = reArrayFiles($_FILES["files"]); if (count($attachments) > MAX_ATTACHMENT_COUNT) - RequestUtils::triggerFormError(__("Too many attachments")); + RequestUtils::triggerFormError(__("Too many attachments"), $formId); // check all attachments before saving one foreach ($attachments as $att) { if ($att["size"] > MAX_ATTACHMENT_SIZE) { - RequestUtils::triggerFormError(__("Individual file size exceeded")); + RequestUtils::triggerFormError(__("Individual file size exceeded"), $formId); } } if (strlen($title) < 1 || strlen($title) > 255) { - RequestUtils::triggerFormError(__("Title too short or too long!")); + RequestUtils::triggerFormError(__("Title too short or too long!"), $formId); } if (strlen($message) < 1 || strlen($message) > 0x8000) { - RequestUtils::triggerFormError(__("Message too short or too long!")); + RequestUtils::triggerFormError(__("Message too short or too long!"), $formId); } $topic = new Topic(); @@ -653,64 +668,125 @@ if ($_action === "auth") { } if (RequestUtils::isRequestMethod("POST")) { - $displayName = RequestUtils::getRequiredField("display_name"); - $pfpAction = RequestUtils::getRequiredField("pfp_action"); + $formId = $_POST["form_id"] ?? null; + if ($formId === null) { + http_response_code(400); + msg_error("Missing form_id"); + exit; + } - $userName = $_POST["name"] ?? $user->name; + if ($formId === "update_password") { + if (!$currentUser) { + http_response_code(403); + msg_error(__("You must be logged in to update your password")); + exit; + } - $user->displayName = $displayName; + if (!$isOwnProfile) { + RequestUtils::triggerFormError(__("You don't have permission to update this user's password"), $formId); + } - $userName = strtolower($userName); - - if ($userName !== $user->name) { - if ($lastNameChangeTooRecent) { - RequestUtils::triggerFormError(__("You can only change your username every 30 days!")); - } else { - if (!ValidationUtils::isUsernameValid($userName)) - RequestUtils::triggerFormError(__("Invalid username!")); - if (!ValidationUtils::isUsernameAvailable($db, $userName)) - RequestUtils::triggerFormError(__("This username is already taken!")); - $user->name = $userName; - $user->nameLastChanged = new DateTimeImmutable(); + RequestUtils::ensureRequestMethod("POST"); + $currentPassword = RequestUtils::getRequiredField("current_password", $formId); + $newPassword = RequestUtils::getRequiredField("new_password", $formId); + $retypePassword = RequestUtils::getRequiredField("retype_password", $formId); + + if (!password_verify($currentPassword, $currentUser->passwordHash)) { + RequestUtils::triggerFormError(__("Current password is incorrect"), $formId); + } + + if ($newPassword !== $retypePassword) { + RequestUtils::triggerFormError(__("New passwords don't match"), $formId); + } + + if (strlen($newPassword) < 8) { + RequestUtils::triggerFormError(__("Password too short! Your password must consist of 8 or more characters"), $formId); } - } - switch ($pfpAction) { - case "keep": - // Do nothing - break; - case "remove": - $user->profilePicture = null; - break; - case "replace": { - if (!isset($_FILES["pfp"]) || $_FILES["pfp"]["error"] !== UPLOAD_ERR_OK) { - RequestUtils::triggerFormError(__("Please upload an image to change your profile picture")); + $currentUser->passwordHash = password_hash($newPassword, PASSWORD_DEFAULT); + + if (!$db->update($currentUser)) { + RequestUtils::triggerFormError(__("Failed to update password"), $formId); + } + + header("Location: $_SERVER[REQUEST_URI]"); + } elseif ($formId === "update_profile") { + if (!$currentUser) { + http_response_code(403); + msg_error(__("You must be logged in to update your profile")); + exit; + } + + $canEdit = ($currentUser?->id === $user?->id && $user?->hasPermission(UserPermissions::EDIT_OWN_USER)) + || ($currentUser?->hasPermission(UserPermissions::EDIT_OTHER_USER)); + + if (!$canEdit) { + http_response_code(403); + msg_error(__("You don't have permission to update this profile")); + exit; + } + + $displayName = RequestUtils::getRequiredField("display_name", $formId); + $pfpAction = RequestUtils::getRequiredField("pfp_action", $formId); + + $userName = $_POST["name"] ?? $user->name; + + $user->displayName = $displayName; + + $userName = strtolower($userName); + + if ($userName !== $user->name) { + if ($lastNameChangeTooRecent) { + RequestUtils::triggerFormError(__("You can only change your username every 30 days!"), $formId); + } else { + if (!ValidationUtils::isUsernameValid($userName)) + RequestUtils::triggerFormError(__("Invalid username!"), $formId); + if (!ValidationUtils::isUsernameAvailable($db, $userName)) + RequestUtils::triggerFormError(__("This username is already taken!"), $formId); + $user->name = $userName; + $user->nameLastChanged = new DateTimeImmutable(); } - $im = @imagecreatefromjpeg($_FILES["pfp"]["tmp_name"]); - if ($im === false) - $im = @imagecreatefrompng($_FILES["pfp"]["tmp_name"]); - if ($im === false) - RequestUtils::triggerFormError(__("Please upload a valid PNG or JPEG file")); - /** @var \GdImage $im */ - $thumb = imagecreatetruecolor(64, 64); - imagecopyresampled($thumb, $im, 0, 0, 0, 0, 64, 64, imagesx($im), imagesy($im)); - imagedestroy($im); - $stream = fopen("php://memory", "w+"); - imagejpeg($thumb, $stream, 50); - rewind($stream); - imagedestroy($thumb); - $user->profilePicture = stream_get_contents($stream); - fclose($stream); - } break; - default: - RequestUtils::triggerFormError("Invalid value for pfp_action"); - break; - } + } - if (!$db->update($user)) - RequestUtils::triggerFormError(__("Failed to save changes", context: "Update profile")); + switch ($pfpAction) { + case "keep": + // Do nothing + break; + case "remove": + $user->profilePicture = null; + break; + case "replace": { + if (!isset($_FILES["pfp"]) || $_FILES["pfp"]["error"] !== UPLOAD_ERR_OK) { + RequestUtils::triggerFormError(__("Please upload an image to change your profile picture"), $formId); + } + $im = @imagecreatefromjpeg($_FILES["pfp"]["tmp_name"]); + if ($im === false) + $im = @imagecreatefrompng($_FILES["pfp"]["tmp_name"]); + if ($im === false) + RequestUtils::triggerFormError(__("Please upload a valid PNG or JPEG file"), $formId); + /** @var \GdImage $im */ + $thumb = imagecreatetruecolor(64, 64); + imagecopyresampled($thumb, $im, 0, 0, 0, 0, 64, 64, imagesx($im), imagesy($im)); + imagedestroy($im); + $stream = fopen("php://memory", "w+"); + imagejpeg($thumb, $stream, 50); + rewind($stream); + imagedestroy($thumb); + $user->profilePicture = stream_get_contents($stream); + fclose($stream); + } break; + default: + RequestUtils::triggerFormError("Invalid value for pfp_action", $formId); + break; + } + + if (!$db->update($user)) + RequestUtils::triggerFormError(__("Failed to save changes", context: "Update profile"), $formId); - header("Location: $_SERVER[REQUEST_URI]"); + header("Location: $_SERVER[REQUEST_URI]"); + } else { + msg_error("Invalid formId"); + } } else { $posts = $db->fetchCustom(Post::class, 'WHERE author_id = $1 ORDER BY post_date DESC', [ $userId ]); $topics = []; @@ -727,7 +803,10 @@ if ($_action === "auth") { } _view("template_start", ["_title" => $user->displayName]); _view("template_navigation_start"); - _view("template_navigation", ["user" => $currentUser]); + _view("template_navigation", [ + "user" => $currentUser, + "isViewingOwnProfile" => $isOwnProfile, + ]); _view("template_navigation_end"); _view("view_user", [ "user" => $user, @@ -915,8 +994,8 @@ if ($_action === "auth") { msg_error("You need to be logged in to delete posts!"); exit; } - - $postId = RequestUtils::getRequiredField("post"); + $formId = "deletepost"; + $postId = RequestUtils::getRequiredField("post", $formId); $post = new Post(); $post->id = $postId; @@ -993,8 +1072,9 @@ if ($_action === "auth") { exit; } - $postId = RequestUtils::getRequiredField("post"); - $message = RequestUtils::getRequiredField("message"); + $formId = "updatepost"; + $postId = RequestUtils::getRequiredField("post", $formId); + $message = RequestUtils::getRequiredField("message", $formId); $post = new Post(); $post->id = $postId; @@ -1041,7 +1121,8 @@ if ($_action === "auth") { exit; } - $topicId = RequestUtils::getRequiredField("topic"); + $formId = "deletetopic"; + $topicId = RequestUtils::getRequiredField("topic", $formId); $topic = new Topic(); $topic->id = $topicId; @@ -1103,8 +1184,9 @@ if ($_action === "auth") { exit; } - $topicId = RequestUtils::getRequiredField("topic"); - $title = RequestUtils::getRequiredField("title"); + $formId = "updatetopic"; + $topicId = RequestUtils::getRequiredField("topic", $formId); + $title = RequestUtils::getRequiredField("title", $formId); $topic = new Topic(); $topic->id = $topicId; @@ -1140,11 +1222,10 @@ if ($_action === "auth") { header("Location: ./?_action=viewtopic&topic=" . urlencode($topicId)); } elseif ($_action === "search") { - if (RequestUtils::isRequestMethod("POST")) { - $query = RequestUtils::getRequiredField("query"); - /** @var Post[] $posts */ - + $query = $_GET["query"] ?? null; + if ($query !== null) { $start_time = microtime(true); + /** @var Post[] $posts */ $posts = $db->execCustomQuery(<<