From af795975721d12feeb8b10a83a3f56e128a4a00e Mon Sep 17 00:00:00 2001 From: "Florine W. Dekker" Date: Mon, 29 Aug 2022 01:09:33 +0200 Subject: [PATCH] Clean up server-side by, like, a lot --- .gitignore | 2 +- Gruntfile.js | 2 +- composer.json | 2 +- composer.lock | Bin 12142 -> 12142 bytes package-lock.json | Bin 225103 -> 225103 bytes package.json | 2 +- src/main/api.php | 265 +++++++++++++---------------- src/main/index.html | 10 +- src/main/php/Database.php | 14 ++ src/main/php/Mailer.php | 2 + src/main/php/Mediawiki.php | 4 +- src/main/php/Response.php | 3 + src/main/php/TrackingManager.php | 83 ++++----- src/main/php/UserManager.php | 195 +++++++-------------- src/main/php/Util.php | 96 +++++++++++ src/main/php/Validator.php | 280 +++++++++++++++++++++++++++++++ 16 files changed, 623 insertions(+), 337 deletions(-) create mode 100644 src/main/php/Util.php create mode 100644 src/main/php/Validator.php diff --git a/.gitignore b/.gitignore index a589b70..920d449 100644 --- a/.gitignore +++ b/.gitignore @@ -119,7 +119,7 @@ dist ## Composer composer.phar -/vendor/ +vendor/ # Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control # You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file diff --git a/Gruntfile.js b/Gruntfile.js index d9d7f6d..f05f367 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -8,7 +8,7 @@ module.exports = grunt => { }, copy: { composer: { - files: [{expand: true, cwd: "./", src: "vendor/**", dest: "dist/", flatten: false}] + files: [{expand: true, cwd: "./vendor/", src: "**", dest: "dist/.vendor/", flatten: false}] }, css: { files: [{expand: true, cwd: "src/main/", src: "**/*.css", dest: "dist/", flatten: true}] diff --git a/composer.json b/composer.json index e2aacee..f303587 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "fwdekker/death-notifier", "description": "Get notified when a famous person dies.", - "version": "0.0.23", + "version": "0.0.24", "type": "project", "license": "MIT", "homepage": "https://git.fwdekker.com/tools/death-notifier", diff --git a/composer.lock b/composer.lock index 254d81d278cf0abaacf7c3c8f3b62d7728d27514..f337106b61f5570149118f75160860087e277c09 100644 GIT binary patch delta 45 zcmaDC_bzUOFr$L8sfDq*rKO>{WooKPN}8!*qKTPBig{{Evaw~Nsj<;!V@7K|089rB ANdN!< delta 45 zcmaDC_bzUOFrz}ExrLdznNeD@nT2_ZrJ+%xnPpO{X;P|@QF3y!nVH#UV@7K|095)7 Aq5uE@ diff --git a/package-lock.json b/package-lock.json index 7f5b6e620f6275becb7942e5e27cfb698694c458..7f1220dbe27fb9fd567cd4d0cab98be57b0620ba 100644 GIT binary patch delta 32 ocmX?qkN5mN-U+6RCKJu3F`7(fpushHandler(new StreamHandler($config["logger"]["file"], $config["logger"]["level"])); -ErrorHandler::register($logger); - -// Connect to database -$db_exists = file_exists($config["database"]["filename"]); +// Preamble +$_POST = Util::parse_post(); +$config = Util::read_config() ?? Util::http_exit(500); +$logger = Util::create_logger($config["logger"]); $conn = Database::connect($config["database"]["filename"]); -// Instantiate utility classes -$mediawiki = new Mediawiki($logger->withName("Mediawiki")); -$user_manager = new UserManager($conn); -$tracking_manager = new TrackingManager($mediawiki, $conn); $mailer = new Mailer($logger->withName("Mailer"), $config, $conn); +$mediawiki = new Mediawiki($logger->withName("Mediawiki")); +$user_manager = new UserManager($conn, $mailer); +$tracking_manager = new TrackingManager($conn, $mailer, $mediawiki); -// Create db if it does not exist -if (!$db_exists) { - $logger->warning("Database does not exist. Creating new database at '{$config["database"]["filename"]}'."); - +if (Database::is_empty($conn)) { + $logger->info("Database does not exist. Creating new database at '{$config["database"]["filename"]}'."); + $mailer->install(); $user_manager->install(); $tracking_manager->install(); - $mailer->install(); -} - -// Start session -/** - * Generates a CSRF token. - * - * @return string the generated CSRF token - */ -function generate_csrf_token(Logger $logger): string -{ - try { - return bin2hex(random_bytes(32)); - } catch (Exception $exception) { - $logger->emergency("Failed to generate token.", ["cause" => $exception]); - http_response_code(500); - exit(); - } } session_start(); -if (!isset($_SESSION["token"])) - $_SESSION["token"] = generate_csrf_token($logger); - -// Read JSON from POST -$post_input = file_get_contents("php://input"); -if ($post_input !== false) $_POST = json_decode($post_input, associative: true); +$_SESSION["token"] = $_SESSION["token"] ?? Util::generate_csrf_token($logger); -/// Define validation helpers -/** - * Validates the user's CSRF token by comparing `$_POST["token"]` against `$_SESSION["token"]`. - * - * @return Response|null `null` if the user's CSRF token is valid, or an error response if either value is not set or if - * the two are not equal - */ -function validate_csrf(): ?Response -{ - if (!(isset($_POST["token"]) && isset($_SESSION["token"]) && hash_equals($_POST["token"], $_SESSION["token"]))) - return new Response( - payload: ["target" => null, "message" => "Invalid CSRF token. Try refreshing the page."], - satisfied: false - ); - - return null; -} - -/** - * Validates that the user is logged out by checking that `$_SESSION["uuid"]` is not set. - * - * @return Response|null `null` if the user is logged out, or an error response otherwise - */ -function validate_logged_out(): ?Response -{ - if (isset($_SESSION["uuid"])) - return new Response(payload: ["target" => null, "message" => "User is already logged in."], satisfied: false); - - return null; -} - -/** - * Validates that the user is logged in by checking that `$_SESSION["uuid"]` is set. - * - * @return Response|null `null` if the user is logged in, or an error response otherwise - */ -function validate_logged_in(): ?Response -{ - if (!isset($_SESSION["uuid"])) - return new Response(payload: ["target" => null, "message" => "User is not logged in."], satisfied: false); - - return null; -} - -/** - * Validates that each of `arguments` is set. - * - * @param mixed ...$arguments the arguments to check - * @return Response|null `null` if all `arguments` are set, or an error response otherwise - */ -function validate_has_arguments(mixed ...$arguments): ?Response -{ - foreach (func_get_args() as $argument) { - if (!isset($argument)) - return new Response(payload: ["target" => null, "message" => "Missing argument."], satisfied: false); - if (!is_string($argument)) - return new Response(payload: ["target" => null, "message" => "Invalid argument type."], satisfied: false); - } - - return null; -} - - -/// Process request +// Process request $response = null; if (isset($_POST["action"])) { // POST requests; alter state switch ($_POST["action"]) { case "register": - $response = validate_csrf() - ?? validate_logged_out() - ?? validate_has_arguments($_POST["email"], $_POST["password"], $_POST["password_confirm"]) - ?? $user_manager->register($mailer, $_POST["email"], $_POST["password"], $_POST["password_confirm"]); + $response = + Validator::validate_inputs($_SESSION, ["uuid" => [new IsNotSetRule()]]) + ?? Validator::validate_inputs($_POST, + [ + "token" => [new EqualsRule($_SESSION["token"])], + "email" => [new IsSetRule(), new EmailRule()], + "password" => [ + new IsSetRule(), + new LengthRule(UserManager::MIN_PASSWORD_LENGTH, UserManager::MAX_PASSWORD_LENGTH) + ], + "password_confirm" => [new IsSetRule()], + ]) + ?? $user_manager->register($_POST["email"], $_POST["password"], $_POST["password_confirm"]); break; case "login": - $response = validate_csrf() - ?? validate_logged_out() - ?? validate_has_arguments($_POST["email"], $_POST["password"]); + $response = + Validator::validate_inputs($_SESSION, ["uuid" => [new IsNotSetRule()]]) + ?? Validator::validate_inputs($_POST, + [ + "token" => [new EqualsRule($_SESSION["token"])], + "email" => [new IsSetRule(), new EmailRule()], + "password" => [ + new IsSetRule(), + new LengthRule(UserManager::MIN_PASSWORD_LENGTH, UserManager::MAX_PASSWORD_LENGTH) + ], + ]); if ($response !== null) break; [$response, $uuid] = $user_manager->check_login($_POST["email"], $_POST["password"]); if ($response->satisfied) $_SESSION["uuid"] = $uuid; break; case "logout": - $response = validate_csrf() ?? validate_logged_in(); + $response = + Validator::validate_inputs($_SESSION, ["uuid" => [new IsSetRule()]]) + ?? Validator::validate_inputs($_POST, ["token" => [new EqualsRule($_SESSION["token"])]]); if ($response !== null) break; session_destroy(); session_start(); - $_SESSION["token"] = generate_csrf_token($logger); + $_SESSION["token"] = Util::generate_csrf_token($logger) ?? Util::http_exit(500); $response = new Response(payload: null, satisfied: true); break; case "update-email": - $response = validate_csrf() - ?? validate_logged_in() - ?? validate_has_arguments($_POST["email"]) - ?? $user_manager->set_email($_SESSION["uuid"], $_POST["email"], $mailer); + $response = + Validator::validate_inputs($_SESSION, ["uuid" => [new IsSetRule()]]) + ?? Validator::validate_inputs($_POST, + [ + "token" => [new EqualsRule($_SESSION["token"])], + "email" => [new IsSetRule(), new EmailRule()], + ]) + ?? $user_manager->set_email($_SESSION["uuid"], $_POST["email"]); break; case "verify-email": - $response = validate_has_arguments($_POST["email"], $_POST["token"]) + $response = + Validator::validate_inputs($_SESSION, ["uuid" => [new IsSetRule()]]) + ?? Validator::validate_inputs($_POST, + [ + "token" => [new IsSetRule()], + "email" => [new IsSetRule(), new EmailRule()], + ]) ?? $user_manager->verify_email($_POST["email"], $_POST["token"]); break; case "resend-verify-email": - $response = validate_csrf() - ?? validate_logged_in() - ?? $user_manager->resend_verify_email($_SESSION["uuid"], $mailer); + $response = + Validator::validate_inputs($_SESSION, ["uuid" => [new IsSetRule()]]) + ?? Validator::validate_inputs($_POST, ["token" => [new EqualsRule($_SESSION["token"])]]) + ?? $user_manager->resend_verify_email($_SESSION["uuid"]); break; case "update-password": - $response = validate_csrf() - ?? validate_logged_in() - ?? validate_has_arguments($_POST["password_old"], $_POST["password_new"], $_POST["password_confirm"]) + $response = + Validator::validate_inputs($_SESSION, ["uuid" => [new IsSetRule()]]) + ?? Validator::validate_inputs($_POST, + [ + "token" => [new EqualsRule($_SESSION["token"])], + "password_old" => [ + new IsSetRule(), + new LengthRule(UserManager::MIN_PASSWORD_LENGTH, UserManager::MAX_PASSWORD_LENGTH) + ], + "password_new" => [ + new IsSetRule(), + new LengthRule(UserManager::MIN_PASSWORD_LENGTH, UserManager::MAX_PASSWORD_LENGTH) + ], + "password_confirm" => [new IsSetRule()], + ]) ?? $user_manager->set_password( $_SESSION["uuid"], $_POST["password_old"], @@ -194,20 +140,37 @@ if (isset($_POST["action"])) { ); break; case "user-delete": - $response = validate_csrf() - ?? validate_logged_in() + $response = + Validator::validate_inputs($_SESSION, ["uuid" => [new IsSetRule()]]) + ?? Validator::validate_inputs($_POST, ["token" => [new EqualsRule($_SESSION["token"])]]) ?? $user_manager->delete($_SESSION["uuid"]); break; case "add-tracking": - $response = validate_csrf() - ?? validate_logged_in() - ?? validate_has_arguments($_POST["person_name"]) + $response = + Validator::validate_inputs($_SESSION, ["uuid" => [new IsSetRule()]]) + ?? Validator::validate_inputs($_POST, + [ + "token" => [new EqualsRule($_SESSION["token"])], + "person_name" => [ + new IsSetRule(), + new LengthRule(TrackingManager::MIN_TITLE_LENGTH, TrackingManager::MAX_TITLE_LENGTH), + new IsNotBlankRule() + ], + ]) ?? $tracking_manager->add_tracking($_SESSION["uuid"], $_POST["person_name"]); break; case "remove-tracking": - $response = validate_csrf() - ?? validate_logged_in() - ?? validate_has_arguments($_POST["person_name"]) + $response = + Validator::validate_inputs($_SESSION, ["uuid" => [new IsSetRule()]]) + ?? Validator::validate_inputs($_POST, + [ + "token" => [new EqualsRule($_SESSION["token"])], + "person_name" => [ + new IsSetRule(), + new LengthRule(TrackingManager::MIN_TITLE_LENGTH, TrackingManager::MAX_TITLE_LENGTH), + new IsNotBlankRule() + ], + ]) ?? $tracking_manager->remove_tracking($_SESSION["uuid"], $_POST["person_name"]); break; default: @@ -230,14 +193,18 @@ if (isset($_POST["action"])) { if (!$response->satisfied) { session_destroy(); session_start(); - $_SESSION["token"] = generate_csrf_token($logger); + $_SESSION["token"] = Util::generate_csrf_token($logger) ?? Util::http_exit(500); } break; case "get-user-data": - $response = validate_logged_in() ?? $user_manager->get_user_data($_SESSION["uuid"]); + $response = + Validator::validate_inputs($_SESSION, ["uuid" => [new IsSetRule()]]) + ?? $user_manager->get_user_data($_SESSION["uuid"]); break; case "list-trackings": - $response = validate_logged_in() ?? $tracking_manager->list_trackings($_SESSION["uuid"]); + $response = + Validator::validate_inputs($_SESSION, ["uuid" => [new IsSetRule()]]) + ?? $tracking_manager->list_trackings($_SESSION["uuid"]); break; default: $response = new Response( @@ -255,7 +222,7 @@ if (isset($_POST["action"])) { switch ($argv[1]) { case "update-all-trackings": $logger->info("Updating all trackings."); - $tracking_manager->update_trackings($mailer, $tracking_manager->list_all_unique_person_names()); + $tracking_manager->update_trackings($tracking_manager->list_all_unique_person_names()); exit("Successfully updated all trackings."); case "process-email-queue": $logger->info("Processing email queue."); diff --git a/src/main/index.html b/src/main/index.html index 3cbefd1..39fdc03 100644 --- a/src/main/index.html +++ b/src/main/index.html @@ -91,7 +91,7 @@ @@ -119,7 +119,7 @@