death-notifier/src/main/api.php

277 lines
12 KiB
PHP

<?php
use com\fwdekker\deathnotifier\Database;
use com\fwdekker\deathnotifier\mailer\Mailer;
use com\fwdekker\deathnotifier\Mediawiki;
use com\fwdekker\deathnotifier\Response;
use com\fwdekker\deathnotifier\trackings\TrackingManager;
use com\fwdekker\deathnotifier\UserManager;
use com\fwdekker\deathnotifier\Util;
use com\fwdekker\deathnotifier\validator\IsEmailRule;
use com\fwdekker\deathnotifier\validator\IsNotBlankRule;
use com\fwdekker\deathnotifier\validator\IsSetRule;
use com\fwdekker\deathnotifier\validator\LengthRule;
use com\fwdekker\deathnotifier\validator\Validator;
/** @noinspection PhpIncludeInspection Exists after `npm run deploy` */
require_once __DIR__ . "/.vendor/autoload.php";
// Preamble
$_POST = Util::parse_post();
$config = Util::read_config() ?? Util::http_exit(500);
// TODO: Improve logging specificity and usefulness
$logger = Util::create_logger($config["logger"]);
$db = new Database($logger->withName("Database"), $config["database"]["filename"]);
$mailer = new Mailer($config, $logger->withName("Mailer"), $db->conn);
$mediawiki = new Mediawiki($logger->withName("Mediawiki"));
$user_manager = new UserManager($logger->withName("UserManager"), $db->conn, $mailer);
$tracking_manager = new TrackingManager($logger->withName("TrackingManager"), $db->conn, $mailer, $mediawiki);
$db->auto_install($mailer, $user_manager, $tracking_manager);
$db->auto_migrate();
session_start();
$_SESSION["token"] = $_SESSION["token"] ?? Util::generate_csrf_token($logger) ?? Util::http_exit(500);
// Process request
$response = null;
if (isset($_POST["action"])) {
// POST requests; alter state
switch ($_POST["action"]) {
case "register":
$response =
Validator::validate_logged_out($_SESSION) ??
Validator::validate_token($_POST, $_SESSION["token"]) ??
Validator::validate_inputs($_POST,
[
"email" => [new IsEmailRule()],
"password" => [
new LengthRule(UserManager::MIN_PASSWORD_LENGTH, UserManager::MAX_PASSWORD_LENGTH)
]
]
) ??
$user_manager->register_user($_POST["email"], $_POST["password"]);
break;
case "login":
$response =
Validator::validate_logged_out($_SESSION) ??
Validator::validate_token($_POST, $_SESSION["token"]) ??
Validator::validate_inputs($_POST, ["email" => [new IsEmailRule()]]);
if ($response !== null) break;
[$response, $uuid] = $user_manager->check_login($_POST["email"], $_POST["password"]);
if ($response->satisfied) $_SESSION["uuid"] = $uuid;
break;
case "logout":
$response =
Validator::validate_logged_in($_SESSION) ??
Validator::validate_token($_POST, $_SESSION["token"]);
if ($response !== null) break;
session_destroy();
session_start();
$_SESSION["token"] = Util::generate_csrf_token($logger) ?? Util::http_exit(500);
$response = Response::satisfied();
break;
case "update-email":
$response =
Validator::validate_logged_in($_SESSION) ??
Validator::validate_token($_POST, $_SESSION["token"]) ??
Validator::validate_inputs($_POST, ["email" => [new IsEmailRule()]]) ??
$user_manager->set_email($_SESSION["uuid"], $_POST["email"]);
break;
case "verify-email":
$response =
// User does not need to be logged in
Validator::validate_token($_POST, $_SESSION["token"]) ??
Validator::validate_inputs($_POST,
[
"email" => [new IsEmailRule()],
"verify_token" => [new IsSetRule()],
]
) ??
$user_manager->verify_email($_POST["email"], $_POST["verify_token"]);
break;
case "resend-verify-email":
$response =
Validator::validate_logged_in($_SESSION) ??
Validator::validate_token($_POST, $_SESSION["token"]) ??
$user_manager->resend_verify_email($_SESSION["uuid"]);
break;
case "toggle-notifications":
$response =
Validator::validate_logged_in($_SESSION) ??
Validator::validate_token($_POST, $_SESSION["token"]) ??
$user_manager->toggle_notifications($_SESSION["uuid"]);
break;
case "send-password-reset":
$response =
// User does not need to be logged in
Validator::validate_token($_POST, $_SESSION["token"]) ??
Validator::validate_inputs($_POST, ["email" => [new IsEmailRule()]]) ??
$user_manager->send_password_reset($_POST["email"]);
break;
case "reset-password":
$response =
// User does not need to be logged in
Validator::validate_token($_POST, $_SESSION["token"]) ??
Validator::validate_inputs($_POST,
[
"password" => [
new LengthRule(UserManager::MIN_PASSWORD_LENGTH, UserManager::MAX_PASSWORD_LENGTH)
],
]
) ??
$user_manager->reset_password($_POST["email"], $_POST["reset_token"], $_POST["password"]);
break;
case "update-password":
$response =
Validator::validate_logged_in($_SESSION) ??
Validator::validate_token($_POST, $_SESSION["token"]) ??
Validator::validate_inputs($_POST,
[
"password_old" => [new IsSetRule()],
"password_new" => [
new LengthRule(UserManager::MIN_PASSWORD_LENGTH, UserManager::MAX_PASSWORD_LENGTH)
],
]
) ??
$user_manager->set_password($_SESSION["uuid"], $_POST["password_old"], $_POST["password_new"]);
break;
case "user-delete":
$response =
Validator::validate_logged_in($_SESSION) ??
Validator::validate_token($_POST, $_SESSION["token"]) ??
$user_manager->delete_user($_SESSION["uuid"]);
session_destroy();
session_start();
$_SESSION["token"] = Util::generate_csrf_token($logger) ?? Util::http_exit(500);
break;
case "add-tracking":
$response =
Validator::validate_logged_in($_SESSION) ??
Validator::validate_token($_POST, $_SESSION["token"]) ??
Validator::validate_inputs($_POST,
[
"person_name" => [
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 =
Validator::validate_logged_in($_SESSION) ??
Validator::validate_token($_POST, $_SESSION["token"]) ??
Validator::validate_inputs($_POST,
[
"person_name" => [
new LengthRule(TrackingManager::MIN_TITLE_LENGTH, TrackingManager::MAX_TITLE_LENGTH),
new IsNotBlankRule()
],
]) ??
$tracking_manager->remove_tracking($_SESSION["uuid"], $_POST["person_name"]);
break;
default:
$response = Response::unsatisfied("Unknown POST action '" . htmlentities($_POST["action"]) . "'.");
break;
}
} elseif (isset($_GET["action"])) {
// GET requests; do not alter state
switch ($_GET["action"]) {
case "start-session":
if (!isset($_SESSION["uuid"])) {
$response = Response::satisfied(["logged_in" => false]);
} else if (!$user_manager->user_exists($_SESSION["uuid"])) {
// User account was deleted
session_destroy();
session_start();
$_SESSION["token"] = Util::generate_csrf_token($logger) ?? Util::http_exit(500);
$response = Response::satisfied(["logged_in" => false]);
} else {
$response = Response::satisfied(["logged_in" => true]);
}
if (isset($config["server"]["global_message"]) && trim($config["server"]["global_message"]) !== "")
$response->payload["global_message"] = trim($config["server"]["global_message"]);
break;
case "validate-password-reset-token":
$response =
// User does not need to be logged in
Validator::validate_token($_GET, $_SESSION["token"]) ??
Validator::validate_inputs($_GET,
[
"reset_token" => [new IsSetRule()],
"email" => [new IsEmailRule()]
]
) ??
$user_manager->validate_password_reset_token($_GET["email"], $_GET["reset_token"]);
break;
case "get-user-data":
$response =
Validator::validate_logged_in($_SESSION) ??
Validator::validate_token($_GET, $_SESSION["token"]) ??
$user_manager->get_user($_SESSION["uuid"]);
break;
case "list-trackings":
$response =
Validator::validate_logged_in($_SESSION) ??
Validator::validate_token($_GET, $_SESSION["token"]) ??
$tracking_manager->list_trackings($_SESSION["uuid"]);
break;
default:
$response = Response::unsatisfied("Unknown GET action '" . htmlentities($_GET["action"]) . "'.");
}
} elseif ($argc > 1) {
// CLI
// TODO: Read secret from file
if (hash_equals($config["admin"]["cli_secret"], "REPLACE THIS WITH A SECRET VALUE"))
exit("Default value for 'cli_secret' detected. Feature disabled.");
if (!hash_equals($config["admin"]["cli_secret"], $argv[2]))
exit("Incorrect value for 'cli_secret'.");
switch ($argv[1]) {
case "emulate-cron":
/* @phpstan-ignore-next-line Intentional infinite loop */
while (true) {
print("Updating all trackings\n");
$tracking_manager->update_trackings($tracking_manager->list_all_unique_person_names());
print("Processing email queue\n");
$mailer->process_queue();
print("Done\n");
sleep(15);
}
case "update-all-trackings":
$logger->info("Updating all trackings.");
$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.");
$mailer->process_queue();
exit("Successfully processed email queue.");
default:
exit("Unknown CLI action '$argv[1]'.");
}
} else {
// No request, no actions, so that's a success
$response = Response::satisfied();
}
// Respond
header("Content-type:application/json;charset=utf-8");
exit(json_encode(array(
"payload" => $response->payload,
"satisfied" => $response->satisfied,
"token" => $_SESSION["token"]
)));