Create action dispatcher mechanism for cleaner api.php
This commit is contained in:
parent
1c62c73055
commit
4fcf615e41
|
@ -1,8 +1,7 @@
|
|||
{
|
||||
"name": "fwdekker/death-notifier",
|
||||
"description": "Get notified when a famous person dies.",
|
||||
"version": "0.14.10",
|
||||
"_comment_version": "Also update version in `package.json`!",
|
||||
"version": "0.15.0", "_comment_version": "Also update version in `package.json`!",
|
||||
"type": "project",
|
||||
"license": "MIT",
|
||||
"homepage": "https://git.fwdekker.com/tools/death-notifier",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "death-notifier",
|
||||
"version": "0.14.11", "_comment_version": "Also update version in `composer.json`!",
|
||||
"version": "0.15.0", "_comment_version": "Also update version in `composer.json`!",
|
||||
"description": "Get notified when a famous person dies.",
|
||||
"author": "Florine W. Dekker",
|
||||
"browser": "dist/bundle.js",
|
||||
|
|
292
src/main/api.php
292
src/main/api.php
|
@ -1,24 +1,40 @@
|
|||
<?php
|
||||
|
||||
use com\fwdekker\deathnotifier\ActionDispatcher;
|
||||
use com\fwdekker\deathnotifier\ActionMethod;
|
||||
use com\fwdekker\deathnotifier\cli\EmulateCronAction;
|
||||
use com\fwdekker\deathnotifier\cli\ProcessEmailQueueAction;
|
||||
use com\fwdekker\deathnotifier\cli\UpdateTrackingsAction;
|
||||
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\StartSessionAction;
|
||||
use com\fwdekker\deathnotifier\trackings\AddTrackingAction;
|
||||
use com\fwdekker\deathnotifier\trackings\ListTrackingsAction;
|
||||
use com\fwdekker\deathnotifier\trackings\RemoveTrackingAction;
|
||||
use com\fwdekker\deathnotifier\trackings\TrackingManager;
|
||||
use com\fwdekker\deathnotifier\UserManager;
|
||||
use com\fwdekker\deathnotifier\user\GetUserDataAction;
|
||||
use com\fwdekker\deathnotifier\user\LoginAction;
|
||||
use com\fwdekker\deathnotifier\user\LogoutAction;
|
||||
use com\fwdekker\deathnotifier\user\RegisterAction;
|
||||
use com\fwdekker\deathnotifier\user\ResendVerifyEmailAction;
|
||||
use com\fwdekker\deathnotifier\user\ResetPasswordAction;
|
||||
use com\fwdekker\deathnotifier\user\SendPasswordResetAction;
|
||||
use com\fwdekker\deathnotifier\user\ToggleNotificationsAction;
|
||||
use com\fwdekker\deathnotifier\user\UpdateEmailAction;
|
||||
use com\fwdekker\deathnotifier\user\UpdatePasswordAction;
|
||||
use com\fwdekker\deathnotifier\user\UserDeleteAction;
|
||||
use com\fwdekker\deathnotifier\user\UserManager;
|
||||
use com\fwdekker\deathnotifier\user\ValidatePasswordResetTokenAction;
|
||||
use com\fwdekker\deathnotifier\user\VerifyEmailAction;
|
||||
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"]);
|
||||
|
@ -33,238 +49,44 @@ $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);
|
||||
$_SESSION["token"] = $_SESSION["token"] ?? Util::generate_csrf_token() ?? Util::http_exit(500);
|
||||
$_POST = Util::parse_post();
|
||||
|
||||
|
||||
// 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
|
||||
$dispatcher = new ActionDispatcher();
|
||||
// GET actions
|
||||
$dispatcher->register_action(new StartSessionAction($user_manager));
|
||||
$dispatcher->register_action(new GetUserDataAction($user_manager));
|
||||
$dispatcher->register_action(new ListTrackingsAction($tracking_manager));
|
||||
$dispatcher->register_action(new ValidatePasswordResetTokenAction($user_manager));
|
||||
// POST actions
|
||||
$dispatcher->register_action(new RegisterAction($user_manager));
|
||||
$dispatcher->register_action(new LoginAction($user_manager));
|
||||
$dispatcher->register_action(new LogoutAction());
|
||||
$dispatcher->register_action(new ResendVerifyEmailAction($user_manager));
|
||||
$dispatcher->register_action(new VerifyEmailAction($user_manager));
|
||||
$dispatcher->register_action(new UpdateEmailAction($user_manager));
|
||||
$dispatcher->register_action(new ToggleNotificationsAction($user_manager));
|
||||
$dispatcher->register_action(new UpdatePasswordAction($user_manager));
|
||||
$dispatcher->register_action(new SendPasswordResetAction($user_manager));
|
||||
$dispatcher->register_action(new ResetPasswordAction($user_manager));
|
||||
$dispatcher->register_action(new UserDeleteAction($user_manager));
|
||||
$dispatcher->register_action(new AddTrackingAction($tracking_manager));
|
||||
$dispatcher->register_action(new RemoveTrackingAction($tracking_manager));
|
||||
// CLI actions
|
||||
$dispatcher->register_action(new UpdateTrackingsAction($config, $tracking_manager));
|
||||
$dispatcher->register_action(new ProcessEmailQueueAction($config, $mailer));
|
||||
$dispatcher->register_action(new EmulateCronAction($config, $tracking_manager, $mailer));
|
||||
// Dispatch
|
||||
if (isset($_GET["action"]))
|
||||
$response = $dispatcher->handle(ActionMethod::GET, $_GET["action"]);
|
||||
else if (isset($_POST["action"]))
|
||||
$response = $dispatcher->handle(ActionMethod::POST, $_POST["action"]);
|
||||
else if ($argc > 1)
|
||||
$response = $dispatcher->handle(ActionMethod::CLI, $argv[1]);
|
||||
else
|
||||
$response = Response::satisfied();
|
||||
}
|
||||
|
||||
|
||||
// Respond
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier;
|
||||
|
||||
use com\fwdekker\deathnotifier\validator\IsEqualToRule;
|
||||
use com\fwdekker\deathnotifier\validator\IsNotSetRule;
|
||||
use com\fwdekker\deathnotifier\validator\IsSetRule;
|
||||
use InvalidArgumentException;
|
||||
|
||||
|
||||
abstract class Action
|
||||
{
|
||||
private readonly bool $require_logged_in;
|
||||
private readonly bool $require_logged_out;
|
||||
private readonly bool $require_valid_csrf_token;
|
||||
private readonly array $rule_lists;
|
||||
|
||||
public readonly ActionMethod $method;
|
||||
public readonly string $action;
|
||||
|
||||
|
||||
public function __construct(ActionMethod $method,
|
||||
string $action,
|
||||
bool $require_logged_in = false,
|
||||
bool $require_logged_out = false,
|
||||
bool $require_valid_csrf_token = false,
|
||||
array $rule_lists = [])
|
||||
{
|
||||
if ($require_logged_in && $require_logged_out)
|
||||
throw new InvalidArgumentException("Cannot require that user is both logged in and logged out.");
|
||||
|
||||
$this->method = $method;
|
||||
$this->action = $action;
|
||||
|
||||
$this->require_logged_in = $require_logged_in;
|
||||
$this->require_logged_out = $require_logged_out;
|
||||
$this->require_valid_csrf_token = $require_valid_csrf_token;
|
||||
$this->rule_lists = $rule_lists;
|
||||
}
|
||||
|
||||
|
||||
final function can_handle(ActionMethod $method, string $action): bool
|
||||
{
|
||||
return $method === $this->method && $action === $this->action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates inputs, throwing an exception if any input is invalid.
|
||||
*
|
||||
* @return void if the input is valid
|
||||
* @throws ValidationException if the input is invalid
|
||||
*/
|
||||
function validate_inputs(): void
|
||||
{
|
||||
$inputs = $this->method->get_inputs();
|
||||
|
||||
if ($this->require_logged_in)
|
||||
(new IsSetRule("You must be logged in to perform this action."))->check($_SESSION, "uuid");
|
||||
if ($this->require_logged_out)
|
||||
(new IsNotSetRule("You must be logged out to perform this action."))->check($_SESSION, "uuid");
|
||||
if ($this->require_valid_csrf_token)
|
||||
(new IsEqualToRule(
|
||||
$_SESSION["token"],
|
||||
"Invalid request token. Please refresh the page and try again."
|
||||
))->check($inputs, "token");
|
||||
|
||||
foreach ($this->rule_lists as $key => $rule_list)
|
||||
foreach ($rule_list as $rule)
|
||||
$rule->check($inputs, $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the action.
|
||||
*
|
||||
* @return mixed the data requested by the action; may be `null`
|
||||
* @throws ActionException if the action could not be performed
|
||||
* @throws ValidationException if the inputs are invalid upon further inspection
|
||||
*/
|
||||
abstract function handle(): mixed;
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier;
|
||||
|
||||
|
||||
class ActionDispatcher
|
||||
{
|
||||
/**
|
||||
* @var array<Action>
|
||||
*/
|
||||
private array $actions = array();
|
||||
|
||||
|
||||
public function register_action(Action $action): void
|
||||
{
|
||||
$this->actions[] = $action;
|
||||
}
|
||||
|
||||
public function handle(ActionMethod $method, string $action_name): Response
|
||||
{
|
||||
$suitable_actions = array_filter($this->actions, fn($action) => $action->can_handle($method, $action_name));
|
||||
if (empty($suitable_actions))
|
||||
return Response::unsatisfied("Unknown $method->name action '$action_name'.");
|
||||
if (sizeof($suitable_actions) > 1)
|
||||
return Response::unsatisfied("Multiple handlers for $method->name action '$action_name'.");
|
||||
|
||||
$action = $suitable_actions[array_key_first($suitable_actions)];
|
||||
try {
|
||||
$action->validate_inputs();
|
||||
$payload = $action->handle();
|
||||
return Response::satisfied($payload);
|
||||
} catch (ValidationException $exception) {
|
||||
return Response::unsatisfied($exception->getMessage(), $exception->target);
|
||||
} catch (ActionException $exception) {
|
||||
return Response::unsatisfied($exception->getMessage(), $exception->target);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier;
|
||||
|
||||
use Exception;
|
||||
|
||||
|
||||
class ActionException extends Exception
|
||||
{
|
||||
public readonly ?string $target;
|
||||
|
||||
|
||||
public function __construct(?string $message = null, ?string $target = null)
|
||||
{
|
||||
parent::__construct($message);
|
||||
|
||||
$this->target = $target;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier;
|
||||
|
||||
|
||||
enum ActionMethod
|
||||
{
|
||||
case CLI;
|
||||
case GET;
|
||||
case POST;
|
||||
|
||||
|
||||
function get_inputs(): array
|
||||
{
|
||||
return match ($this) {
|
||||
ActionMethod::CLI => $_SERVER["argv"],
|
||||
ActionMethod::GET => $_GET,
|
||||
ActionMethod::POST => $_POST,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ namespace com\fwdekker\deathnotifier;
|
|||
|
||||
use com\fwdekker\deathnotifier\mailer\Mailer;
|
||||
use com\fwdekker\deathnotifier\trackings\TrackingManager;
|
||||
use com\fwdekker\deathnotifier\user\UserManager;
|
||||
use Composer\Semver\Comparator;
|
||||
use Monolog\Logger;
|
||||
use PDO;
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier;
|
||||
|
||||
use com\fwdekker\deathnotifier\user\UserManager;
|
||||
use Exception;
|
||||
|
||||
|
||||
class StartSessionAction extends Action
|
||||
{
|
||||
private readonly UserManager $user_manager;
|
||||
|
||||
|
||||
public function __construct(UserManager $user_manager)
|
||||
{
|
||||
parent::__construct(ActionMethod::GET, "start-session");
|
||||
|
||||
$this->user_manager = $user_manager;
|
||||
}
|
||||
|
||||
|
||||
function handle(): array
|
||||
{
|
||||
$payload = [];
|
||||
|
||||
// Check if user is logged in
|
||||
if (!isset($_SESSION["uuid"])) {
|
||||
$payload["logged_in"] = false;
|
||||
} else if (!$this->user_manager->user_exists($_SESSION["uuid"])) {
|
||||
// User account was deleted
|
||||
session_destroy();
|
||||
session_start();
|
||||
try {
|
||||
$_SESSION["token"] = Util::generate_csrf_token();
|
||||
} catch (Exception) {
|
||||
throw new ActionException("Failed to generate new CSRF token. Please try again later.", null);
|
||||
}
|
||||
|
||||
$payload["logged_in"] = false;
|
||||
} else {
|
||||
$payload["logged_in"] = true;
|
||||
}
|
||||
|
||||
// Read global message
|
||||
if (isset($config["server"]["global_message"]) && trim($config["server"]["global_message"]) !== "")
|
||||
$payload["global_message"] = trim($config["server"]["global_message"]);
|
||||
|
||||
return $payload;
|
||||
}
|
||||
}
|
|
@ -82,16 +82,11 @@ class Util
|
|||
/**
|
||||
* Generates an appropriate CSRF token.
|
||||
*
|
||||
* @param Logger $logger the logger to use if something goes wrong
|
||||
* @return string|null the CSRF token, or `null` if no CSRF token could be created
|
||||
* @return string the generated CSRF token
|
||||
* @throws Exception if the CSRF token could not be generated
|
||||
*/
|
||||
static function generate_csrf_token(Logger $logger): ?string
|
||||
static function generate_csrf_token(): string
|
||||
{
|
||||
try {
|
||||
return bin2hex(random_bytes(32));
|
||||
} catch (Exception $exception) {
|
||||
$logger->emergency("Failed to generate token.", ["cause" => $exception]);
|
||||
return null;
|
||||
}
|
||||
return bin2hex(random_bytes(32));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\cli;
|
||||
|
||||
use com\fwdekker\deathnotifier\Action;
|
||||
use com\fwdekker\deathnotifier\ActionMethod;
|
||||
use com\fwdekker\deathnotifier\ValidationException;
|
||||
|
||||
|
||||
abstract class CliAction extends Action
|
||||
{
|
||||
private mixed $config;
|
||||
|
||||
|
||||
public function __construct(mixed $config, string $action, array $rule_lists = [])
|
||||
{
|
||||
parent::__construct(ActionMethod::CLI, $action, rule_lists: $rule_lists);
|
||||
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
|
||||
public function validate_inputs(): void
|
||||
{
|
||||
$inputs = $this->method->get_inputs();
|
||||
// TODO: Read secret from file specified from `inputs`
|
||||
if (hash_equals($this->config["admin"]["cli_secret"], "REPLACE THIS WITH A SECRET VALUE"))
|
||||
throw new ValidationException("Default value for 'cli_secret' detected. Feature disabled.");
|
||||
if (!hash_equals($this->config["admin"]["cli_secret"], $inputs[2]))
|
||||
throw new ValidationException("Incorrect value for 'cli_secret'.");
|
||||
|
||||
parent::validate_inputs();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\cli;
|
||||
|
||||
use com\fwdekker\deathnotifier\mailer\Mailer;
|
||||
use com\fwdekker\deathnotifier\trackings\TrackingManager;
|
||||
|
||||
|
||||
class EmulateCronAction extends CliAction
|
||||
{
|
||||
private readonly TrackingManager $tracking_manager;
|
||||
private readonly Mailer $mailer;
|
||||
|
||||
|
||||
public function __construct(mixed $config, TrackingManager $tracking_manager, Mailer $mailer)
|
||||
{
|
||||
parent::__construct($config, "update-trackings");
|
||||
|
||||
$this->tracking_manager = $tracking_manager;
|
||||
$this->mailer = $mailer;
|
||||
}
|
||||
|
||||
|
||||
public function handle(): string
|
||||
{
|
||||
while (true) {
|
||||
print("Updating all trackings\n");
|
||||
$this->tracking_manager->update_trackings($this->tracking_manager->list_all_unique_person_names());
|
||||
print("Processing email queue\n");
|
||||
$this->mailer->process_queue();
|
||||
print("Done\n");
|
||||
sleep(15);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\cli;
|
||||
|
||||
use com\fwdekker\deathnotifier\mailer\Mailer;
|
||||
|
||||
|
||||
class ProcessEmailQueueAction extends CliAction
|
||||
{
|
||||
private readonly Mailer $mailer;
|
||||
|
||||
|
||||
public function __construct(mixed $config, Mailer $mailer)
|
||||
{
|
||||
parent::__construct($config, "process-email-queue");
|
||||
|
||||
$this->mailer = $mailer;
|
||||
}
|
||||
|
||||
|
||||
public function handle(): string
|
||||
{
|
||||
$this->mailer->process_queue();
|
||||
|
||||
return "Successfully processed email queue.";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\cli;
|
||||
|
||||
use com\fwdekker\deathnotifier\trackings\TrackingManager;
|
||||
|
||||
|
||||
class UpdateTrackingsAction extends CliAction
|
||||
{
|
||||
private readonly TrackingManager $tracking_manager;
|
||||
|
||||
|
||||
public function __construct(mixed $config, TrackingManager $tracking_manager)
|
||||
{
|
||||
parent::__construct($config, "update-trackings");
|
||||
|
||||
$this->tracking_manager = $tracking_manager;
|
||||
}
|
||||
|
||||
|
||||
public function handle(): string
|
||||
{
|
||||
$this->tracking_manager->update_trackings($this->tracking_manager->list_all_unique_person_names());
|
||||
|
||||
return "Successfully updated all trackings.";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\trackings;
|
||||
|
||||
use com\fwdekker\deathnotifier\Action;
|
||||
use com\fwdekker\deathnotifier\ActionException;
|
||||
use com\fwdekker\deathnotifier\ActionMethod;
|
||||
use com\fwdekker\deathnotifier\validator\HasLengthRule;
|
||||
use com\fwdekker\deathnotifier\validator\IsNotBlankRule;
|
||||
|
||||
|
||||
class AddTrackingAction extends Action
|
||||
{
|
||||
private readonly TrackingManager $tracking_manager;
|
||||
|
||||
|
||||
public function __construct(TrackingManager $tracking_manager)
|
||||
{
|
||||
parent::__construct(
|
||||
ActionMethod::POST,
|
||||
"add-tracking",
|
||||
require_logged_in: true,
|
||||
require_valid_csrf_token: true,
|
||||
rule_lists: [
|
||||
"person_name" => [
|
||||
new IsNotBlankRule(),
|
||||
new HasLengthRule(TrackingManager::MIN_TITLE_LENGTH, TrackingManager::MAX_TITLE_LENGTH)
|
||||
],
|
||||
],
|
||||
);
|
||||
|
||||
$this->tracking_manager = $tracking_manager;
|
||||
}
|
||||
|
||||
|
||||
function handle(): mixed
|
||||
{
|
||||
$response = $this->tracking_manager->add_tracking($_SESSION["uuid"], $_POST["person_name"]);
|
||||
if (!$response->satisfied)
|
||||
throw new ActionException($response->payload["message"], $response->payload["target"]);
|
||||
|
||||
return $response->payload;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\trackings;
|
||||
|
||||
use com\fwdekker\deathnotifier\Action;
|
||||
use com\fwdekker\deathnotifier\ActionException;
|
||||
use com\fwdekker\deathnotifier\ActionMethod;
|
||||
|
||||
|
||||
class ListTrackingsAction extends Action
|
||||
{
|
||||
private readonly TrackingManager $tracking_manager;
|
||||
|
||||
|
||||
public function __construct(TrackingManager $tracking_manager)
|
||||
{
|
||||
parent::__construct(
|
||||
ActionMethod::GET,
|
||||
"list-trackings",
|
||||
require_logged_in: true,
|
||||
require_valid_csrf_token: true,
|
||||
);
|
||||
|
||||
$this->tracking_manager = $tracking_manager;
|
||||
}
|
||||
|
||||
|
||||
function handle(): mixed
|
||||
{
|
||||
$response = $this->tracking_manager->list_trackings($_SESSION["uuid"]);
|
||||
if (!$response->satisfied)
|
||||
throw new ActionException($response->payload["message"], $response->payload["target"]);
|
||||
|
||||
return $response->payload;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\trackings;
|
||||
|
||||
use com\fwdekker\deathnotifier\Action;
|
||||
use com\fwdekker\deathnotifier\ActionException;
|
||||
use com\fwdekker\deathnotifier\ActionMethod;
|
||||
use com\fwdekker\deathnotifier\validator\IsNotBlankRule;
|
||||
|
||||
|
||||
class RemoveTrackingAction extends Action
|
||||
{
|
||||
private readonly TrackingManager $tracking_manager;
|
||||
|
||||
|
||||
public function __construct(TrackingManager $tracking_manager)
|
||||
{
|
||||
parent::__construct(
|
||||
ActionMethod::POST,
|
||||
"remove-tracking",
|
||||
require_logged_in: true,
|
||||
require_valid_csrf_token: true,
|
||||
rule_lists: ["person_name" => [new IsNotBlankRule()]],
|
||||
);
|
||||
|
||||
$this->tracking_manager = $tracking_manager;
|
||||
}
|
||||
|
||||
|
||||
function handle(): mixed
|
||||
{
|
||||
$response = $this->tracking_manager->remove_tracking($_SESSION["uuid"], $_POST["person_name"]);
|
||||
if (!$response->satisfied)
|
||||
throw new ActionException($response->payload["message"], $response->payload["target"]);
|
||||
|
||||
return $response->payload;
|
||||
}
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\mailer;
|
||||
namespace com\fwdekker\deathnotifier\user;
|
||||
|
||||
use com\fwdekker\deathnotifier\UserManager;
|
||||
use com\fwdekker\deathnotifier\mailer\Email;
|
||||
|
||||
|
||||
/**
|
|
@ -1,6 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\mailer;
|
||||
namespace com\fwdekker\deathnotifier\user;
|
||||
|
||||
use com\fwdekker\deathnotifier\mailer\Email;
|
||||
|
||||
|
||||
/**
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\user;
|
||||
|
||||
use com\fwdekker\deathnotifier\Action;
|
||||
use com\fwdekker\deathnotifier\ActionException;
|
||||
use com\fwdekker\deathnotifier\ActionMethod;
|
||||
|
||||
|
||||
class GetUserDataAction extends Action
|
||||
{
|
||||
private readonly UserManager $user_manager;
|
||||
|
||||
|
||||
public function __construct(UserManager $user_manager)
|
||||
{
|
||||
parent::__construct(
|
||||
ActionMethod::GET,
|
||||
"get-user-data",
|
||||
require_logged_in: true,
|
||||
require_valid_csrf_token: true,
|
||||
);
|
||||
|
||||
$this->user_manager = $user_manager;
|
||||
}
|
||||
|
||||
|
||||
function handle(): mixed
|
||||
{
|
||||
$response = $this->user_manager->get_user($_SESSION["uuid"]);
|
||||
if (!$response->satisfied)
|
||||
throw new ActionException($response->payload["message"], $response->payload["target"]);
|
||||
|
||||
return $response->payload;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\user;
|
||||
|
||||
use com\fwdekker\deathnotifier\Action;
|
||||
use com\fwdekker\deathnotifier\ActionMethod;
|
||||
use com\fwdekker\deathnotifier\validator\IsEmailRule;
|
||||
|
||||
|
||||
class LoginAction extends Action
|
||||
{
|
||||
private readonly UserManager $user_manager;
|
||||
|
||||
|
||||
public function __construct(UserManager $user_manager)
|
||||
{
|
||||
parent::__construct(
|
||||
ActionMethod::POST,
|
||||
"login",
|
||||
require_logged_out: true,
|
||||
require_valid_csrf_token: true,
|
||||
rule_lists: ["email" => [new IsEmailRule()]],
|
||||
);
|
||||
|
||||
$this->user_manager = $user_manager;
|
||||
}
|
||||
|
||||
|
||||
function handle(): mixed
|
||||
{
|
||||
[$response, $uuid] = $this->user_manager->check_login($_POST["email"], $_POST["password"]);
|
||||
if ($response->satisfied) $_SESSION["uuid"] = $uuid;
|
||||
|
||||
return $response->payload;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\user;
|
||||
|
||||
use com\fwdekker\deathnotifier\Action;
|
||||
use com\fwdekker\deathnotifier\ActionException;
|
||||
use com\fwdekker\deathnotifier\ActionMethod;
|
||||
use com\fwdekker\deathnotifier\Util;
|
||||
use com\fwdekker\deathnotifier\validator\IsEmailRule;
|
||||
use Exception;
|
||||
use Monolog\Logger;
|
||||
|
||||
|
||||
class LogoutAction extends Action
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(
|
||||
ActionMethod::POST,
|
||||
"logout",
|
||||
require_logged_in: true,
|
||||
require_valid_csrf_token: true,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function handle(): mixed
|
||||
{
|
||||
session_destroy();
|
||||
session_start();
|
||||
try {
|
||||
$_SESSION["token"] = Util::generate_csrf_token();
|
||||
} catch (Exception) {
|
||||
throw new ActionException("Failed to generate new CSRF token. Please try again later.", null);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\user;
|
||||
|
||||
use com\fwdekker\deathnotifier\Action;
|
||||
use com\fwdekker\deathnotifier\ActionException;
|
||||
use com\fwdekker\deathnotifier\ActionMethod;
|
||||
use com\fwdekker\deathnotifier\validator\HasLengthRule;
|
||||
use com\fwdekker\deathnotifier\validator\IsEmailRule;
|
||||
|
||||
|
||||
class RegisterAction extends Action
|
||||
{
|
||||
private readonly UserManager $user_manager;
|
||||
|
||||
|
||||
public function __construct(UserManager $user_manager)
|
||||
{
|
||||
parent::__construct(
|
||||
ActionMethod::POST,
|
||||
"register",
|
||||
require_logged_out: true,
|
||||
require_valid_csrf_token: true,
|
||||
rule_lists: [
|
||||
"email" => [new IsEmailRule()],
|
||||
"password" => [new HasLengthRule(UserManager::MIN_PASSWORD_LENGTH, UserManager::MAX_PASSWORD_LENGTH)],
|
||||
],
|
||||
);
|
||||
|
||||
$this->user_manager = $user_manager;
|
||||
}
|
||||
|
||||
function handle(): mixed
|
||||
{
|
||||
$response = $this->user_manager->register_user($_POST["email"], $_POST["password"]);
|
||||
if (!$response->satisfied)
|
||||
throw new ActionException($response->payload["message"], $response->payload["target"]);
|
||||
|
||||
return $response->payload;
|
||||
}
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\mailer;
|
||||
namespace com\fwdekker\deathnotifier\user;
|
||||
|
||||
use com\fwdekker\deathnotifier\UserManager;
|
||||
use com\fwdekker\deathnotifier\mailer\Email;
|
||||
|
||||
|
||||
/**
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\user;
|
||||
|
||||
use com\fwdekker\deathnotifier\Action;
|
||||
use com\fwdekker\deathnotifier\ActionException;
|
||||
use com\fwdekker\deathnotifier\ActionMethod;
|
||||
|
||||
|
||||
class ResendVerifyEmailAction extends Action
|
||||
{
|
||||
private readonly UserManager $user_manager;
|
||||
|
||||
|
||||
public function __construct(UserManager $user_manager)
|
||||
{
|
||||
parent::__construct(
|
||||
ActionMethod::POST,
|
||||
"resend-verify-email",
|
||||
require_logged_in: true,
|
||||
require_valid_csrf_token: true,
|
||||
);
|
||||
|
||||
$this->user_manager = $user_manager;
|
||||
}
|
||||
|
||||
|
||||
function handle(): mixed
|
||||
{
|
||||
$response = $this->user_manager->resend_verify_email($_SESSION["uuid"]);
|
||||
if (!$response->satisfied)
|
||||
throw new ActionException($response->payload["message"], $response->payload["target"]);
|
||||
|
||||
return $response->payload;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\user;
|
||||
|
||||
use com\fwdekker\deathnotifier\Action;
|
||||
use com\fwdekker\deathnotifier\ActionException;
|
||||
use com\fwdekker\deathnotifier\ActionMethod;
|
||||
use com\fwdekker\deathnotifier\validator\HasLengthRule;
|
||||
|
||||
|
||||
class ResetPasswordAction extends Action
|
||||
{
|
||||
private readonly UserManager $user_manager;
|
||||
|
||||
|
||||
public function __construct(UserManager $user_manager)
|
||||
{
|
||||
parent::__construct(
|
||||
ActionMethod::POST,
|
||||
"reset-password",
|
||||
require_valid_csrf_token: true,
|
||||
rule_lists: [
|
||||
"password" => [new HasLengthRule(UserManager::MIN_PASSWORD_LENGTH, UserManager::MAX_PASSWORD_LENGTH)],
|
||||
],
|
||||
);
|
||||
|
||||
$this->user_manager = $user_manager;
|
||||
}
|
||||
|
||||
|
||||
function handle(): mixed
|
||||
{
|
||||
$response = $this->user_manager->reset_password($_POST["email"], $_POST["reset_token"], $_POST["password"]);
|
||||
if (!$response->satisfied)
|
||||
throw new ActionException($response->payload["message"], $response->payload["target"]);
|
||||
|
||||
return $response->payload;
|
||||
}
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\mailer;
|
||||
namespace com\fwdekker\deathnotifier\user;
|
||||
|
||||
use com\fwdekker\deathnotifier\UserManager;
|
||||
use com\fwdekker\deathnotifier\mailer\Email;
|
||||
|
||||
|
||||
/**
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\user;
|
||||
|
||||
use com\fwdekker\deathnotifier\Action;
|
||||
use com\fwdekker\deathnotifier\ActionException;
|
||||
use com\fwdekker\deathnotifier\ActionMethod;
|
||||
use com\fwdekker\deathnotifier\validator\IsEmailRule;
|
||||
|
||||
|
||||
class SendPasswordResetAction extends Action
|
||||
{
|
||||
private readonly UserManager $user_manager;
|
||||
|
||||
|
||||
public function __construct(UserManager $user_manager)
|
||||
{
|
||||
parent::__construct(
|
||||
ActionMethod::POST,
|
||||
"send-password-reset",
|
||||
require_valid_csrf_token: true,
|
||||
rule_lists: ["email" => [new IsEmailRule()]],
|
||||
);
|
||||
|
||||
$this->user_manager = $user_manager;
|
||||
}
|
||||
|
||||
|
||||
function handle(): mixed
|
||||
{
|
||||
$response = $this->user_manager->send_password_reset($_POST["email"]);
|
||||
if (!$response->satisfied)
|
||||
throw new ActionException($response->payload["message"], $response->payload["target"]);
|
||||
|
||||
return $response->payload;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\user;
|
||||
|
||||
use com\fwdekker\deathnotifier\Action;
|
||||
use com\fwdekker\deathnotifier\ActionException;
|
||||
use com\fwdekker\deathnotifier\ActionMethod;
|
||||
|
||||
|
||||
class ToggleNotificationsAction extends Action
|
||||
{
|
||||
private readonly UserManager $user_manager;
|
||||
|
||||
|
||||
public function __construct(UserManager $user_manager)
|
||||
{
|
||||
parent::__construct(
|
||||
ActionMethod::POST,
|
||||
"toggle-notifications",
|
||||
require_logged_in: true,
|
||||
require_valid_csrf_token: true,
|
||||
);
|
||||
|
||||
$this->user_manager = $user_manager;
|
||||
}
|
||||
|
||||
|
||||
function handle(): mixed
|
||||
{
|
||||
$response = $this->user_manager->toggle_notifications($_SESSION["uuid"]);
|
||||
if (!$response->satisfied)
|
||||
throw new ActionException($response->payload["message"], $response->payload["target"]);
|
||||
|
||||
return $response->payload;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\user;
|
||||
|
||||
use com\fwdekker\deathnotifier\Action;
|
||||
use com\fwdekker\deathnotifier\ActionException;
|
||||
use com\fwdekker\deathnotifier\ActionMethod;
|
||||
use com\fwdekker\deathnotifier\validator\IsEmailRule;
|
||||
|
||||
|
||||
class UpdateEmailAction extends Action
|
||||
{
|
||||
private readonly UserManager $user_manager;
|
||||
|
||||
|
||||
public function __construct(UserManager $user_manager)
|
||||
{
|
||||
parent::__construct(
|
||||
ActionMethod::POST,
|
||||
"update-email",
|
||||
require_logged_in: true,
|
||||
require_valid_csrf_token: true,
|
||||
rule_lists: ["email" => [new IsEmailRule()]],
|
||||
);
|
||||
|
||||
$this->user_manager = $user_manager;
|
||||
}
|
||||
|
||||
|
||||
function handle(): mixed
|
||||
{
|
||||
$response = $this->user_manager->set_email($_SESSION["uuid"], $_POST["email"]);
|
||||
if (!$response->satisfied)
|
||||
throw new ActionException($response->payload["message"], $response->payload["target"]);
|
||||
|
||||
return $response->payload;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\user;
|
||||
|
||||
use com\fwdekker\deathnotifier\Action;
|
||||
use com\fwdekker\deathnotifier\ActionException;
|
||||
use com\fwdekker\deathnotifier\ActionMethod;
|
||||
use com\fwdekker\deathnotifier\validator\HasLengthRule;
|
||||
use com\fwdekker\deathnotifier\validator\IsSetRule;
|
||||
|
||||
|
||||
class UpdatePasswordAction extends Action
|
||||
{
|
||||
private readonly UserManager $user_manager;
|
||||
|
||||
|
||||
public function __construct(UserManager $user_manager)
|
||||
{
|
||||
parent::__construct(
|
||||
ActionMethod::POST,
|
||||
"update-password",
|
||||
require_logged_in: true,
|
||||
require_valid_csrf_token: true,
|
||||
rule_lists: [
|
||||
"password_old" => [new IsSetRule()],
|
||||
"password_new" => [
|
||||
new HasLengthRule(UserManager::MIN_PASSWORD_LENGTH, UserManager::MAX_PASSWORD_LENGTH)
|
||||
],
|
||||
],
|
||||
);
|
||||
|
||||
$this->user_manager = $user_manager;
|
||||
}
|
||||
|
||||
|
||||
function handle(): mixed
|
||||
{
|
||||
$response = $this->user_manager->set_password($_SESSION["uuid"], $_POST["password_old"], $_POST["password_new"]);
|
||||
if (!$response->satisfied)
|
||||
throw new ActionException($response->payload["message"], $response->payload["target"]);
|
||||
|
||||
return $response->payload;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\user;
|
||||
|
||||
use com\fwdekker\deathnotifier\Action;
|
||||
use com\fwdekker\deathnotifier\ActionException;
|
||||
use com\fwdekker\deathnotifier\ActionMethod;
|
||||
use com\fwdekker\deathnotifier\Util;
|
||||
use com\fwdekker\deathnotifier\validator\HasLengthRule;
|
||||
use com\fwdekker\deathnotifier\validator\IsSetRule;
|
||||
use Exception;
|
||||
|
||||
|
||||
class UserDeleteAction extends Action
|
||||
{
|
||||
private readonly UserManager $user_manager;
|
||||
|
||||
|
||||
public function __construct(UserManager $user_manager)
|
||||
{
|
||||
parent::__construct(
|
||||
ActionMethod::POST,
|
||||
"user-delete",
|
||||
require_logged_in: true,
|
||||
require_valid_csrf_token: true,
|
||||
rule_lists: [
|
||||
"password_old" => [new IsSetRule()],
|
||||
"password_new" => [
|
||||
new HasLengthRule(UserManager::MIN_PASSWORD_LENGTH, UserManager::MAX_PASSWORD_LENGTH)
|
||||
],
|
||||
],
|
||||
);
|
||||
|
||||
$this->user_manager = $user_manager;
|
||||
}
|
||||
|
||||
|
||||
function handle(): mixed
|
||||
{
|
||||
$response = $this->user_manager->delete_user($_SESSION["uuid"]);
|
||||
if (!$response->satisfied)
|
||||
throw new ActionException($response->payload["message"], $response->payload["target"]);
|
||||
|
||||
session_destroy();
|
||||
session_start();
|
||||
try {
|
||||
$_SESSION["token"] = Util::generate_csrf_token();
|
||||
} catch (Exception) {
|
||||
throw new ActionException("Failed to generate new CSRF token. Please try again later.", null);
|
||||
}
|
||||
|
||||
return $response->payload;
|
||||
}
|
||||
}
|
|
@ -1,14 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier;
|
||||
namespace com\fwdekker\deathnotifier\user;
|
||||
|
||||
use com\fwdekker\deathnotifier\mailer\ChangedEmailEmail;
|
||||
use com\fwdekker\deathnotifier\mailer\ChangedPasswordEmail;
|
||||
use com\fwdekker\deathnotifier\Database;
|
||||
use com\fwdekker\deathnotifier\mailer\Mailer;
|
||||
use com\fwdekker\deathnotifier\mailer\RegisterEmail;
|
||||
use com\fwdekker\deathnotifier\mailer\ResetPasswordEmail;
|
||||
use com\fwdekker\deathnotifier\mailer\VerifyEmailEmail;
|
||||
use DateTime;
|
||||
use com\fwdekker\deathnotifier\Response;
|
||||
use Monolog\Logger;
|
||||
use PDO;
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\user;
|
||||
|
||||
use com\fwdekker\deathnotifier\Action;
|
||||
use com\fwdekker\deathnotifier\ActionException;
|
||||
use com\fwdekker\deathnotifier\ActionMethod;
|
||||
use com\fwdekker\deathnotifier\validator\IsEmailRule;
|
||||
use com\fwdekker\deathnotifier\validator\IsSetRule;
|
||||
|
||||
|
||||
class ValidatePasswordResetTokenAction extends Action
|
||||
{
|
||||
private readonly UserManager $user_manager;
|
||||
|
||||
|
||||
public function __construct(UserManager $user_manager)
|
||||
{
|
||||
parent::__construct(
|
||||
ActionMethod::GET,
|
||||
"validate-password-reset-token",
|
||||
require_valid_csrf_token: true,
|
||||
rule_lists: [
|
||||
"reset_token" => [new IsSetRule()],
|
||||
"email" => [new IsEmailRule()],
|
||||
],
|
||||
);
|
||||
|
||||
$this->user_manager = $user_manager;
|
||||
}
|
||||
|
||||
|
||||
function handle(): mixed
|
||||
{
|
||||
$response = $this->user_manager->validate_password_reset_token($_GET["email"], $_GET["reset_token"]);
|
||||
if (!$response->satisfied)
|
||||
throw new ActionException($response->payload["message"], $response->payload["target"]);
|
||||
|
||||
return $response->payload;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\user;
|
||||
|
||||
use com\fwdekker\deathnotifier\Action;
|
||||
use com\fwdekker\deathnotifier\ActionException;
|
||||
use com\fwdekker\deathnotifier\ActionMethod;
|
||||
use com\fwdekker\deathnotifier\validator\IsEmailRule;
|
||||
use com\fwdekker\deathnotifier\validator\IsSetRule;
|
||||
|
||||
|
||||
class VerifyEmailAction extends Action
|
||||
{
|
||||
private readonly UserManager $user_manager;
|
||||
|
||||
|
||||
public function __construct(UserManager $user_manager)
|
||||
{
|
||||
parent::__construct(
|
||||
ActionMethod::POST,
|
||||
"verify-email",
|
||||
require_valid_csrf_token: true,
|
||||
rule_lists: [
|
||||
"email" => [new IsEmailRule()],
|
||||
"verify_token" => [new IsSetRule()],
|
||||
],
|
||||
);
|
||||
|
||||
$this->user_manager = $user_manager;
|
||||
}
|
||||
|
||||
|
||||
function handle(): mixed
|
||||
{
|
||||
$response = $this->user_manager->verify_email($_POST["email"], $_POST["verify_token"]);
|
||||
if (!$response->satisfied)
|
||||
throw new ActionException($response->payload["message"], $response->payload["target"]);
|
||||
|
||||
return $response->payload;
|
||||
}
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\mailer;
|
||||
namespace com\fwdekker\deathnotifier\user;
|
||||
|
||||
use com\fwdekker\deathnotifier\UserManager;
|
||||
use com\fwdekker\deathnotifier\mailer\Email;
|
||||
|
||||
|
||||
/**
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
namespace com\fwdekker\deathnotifier\validator;
|
||||
|
||||
use com\fwdekker\deathnotifier\Response;
|
||||
use com\fwdekker\deathnotifier\ValidationException;
|
||||
|
||||
|
||||
/**
|
||||
* Requires the input to be of a specific length.
|
||||
* Verifies that the input is of the specific length.
|
||||
*/
|
||||
class LengthRule extends Rule
|
||||
class HasLengthRule extends Rule
|
||||
{
|
||||
/**
|
||||
* @var int|null The minimum length (inclusive), or `null` if there is no minimum length.
|
||||
|
@ -21,7 +21,7 @@ class LengthRule extends Rule
|
|||
|
||||
|
||||
/**
|
||||
* Instantiates a new rule.
|
||||
* Verifies that the input is of the specific length.
|
||||
*
|
||||
* @param int|null $min_length the minimum length (inclusive), or `null` if there is no minimum length
|
||||
* @param int|null $max_length the maximum length (inclusive), or `null` if there is no maximum length
|
||||
|
@ -38,30 +38,27 @@ class LengthRule extends Rule
|
|||
|
||||
|
||||
/**
|
||||
* Checks whether the input is of the specified length.
|
||||
* Verifies that the input is of the specific length.
|
||||
*
|
||||
* @param array<string, mixed> $inputs the list of inputs in which the value at `key` should be checked
|
||||
* @param string $key the key in `inputs` of the input to check
|
||||
* @return Response|null `null` if the input is of the specified length, or an unsatisfied `Response` otherwise
|
||||
* @return void if `$inputs[$key]` is of the specified length
|
||||
* @throws ValidationException if `$inputs[$key]` is not set or is not of the specified length
|
||||
*/
|
||||
public function check(array $inputs, string $key): ?Response
|
||||
public function check(array $inputs, string $key): void
|
||||
{
|
||||
if (!isset($inputs[$key]))
|
||||
return Response::unsatisfied(
|
||||
$this->override_message ?? "Missing input '$key'.",
|
||||
$key
|
||||
);
|
||||
else if ($this->min_length !== null && strlen($inputs[$key]) < $this->min_length)
|
||||
return Response::unsatisfied(
|
||||
throw new ValidationException($this->override_message ?? "Missing input '$key'.", $key);
|
||||
|
||||
if ($this->min_length !== null && strlen($inputs[$key]) < $this->min_length)
|
||||
throw new ValidationException(
|
||||
$this->override_message ?? "Use at least $this->min_length character(s).",
|
||||
$key
|
||||
);
|
||||
else if ($this->max_length !== null && strlen($inputs[$key]) > $this->max_length)
|
||||
return Response::unsatisfied(
|
||||
if ($this->max_length !== null && strlen($inputs[$key]) > $this->max_length)
|
||||
throw new ValidationException(
|
||||
$this->override_message ?? "Use at most $this->max_length character(s).",
|
||||
$key
|
||||
);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -2,25 +2,25 @@
|
|||
|
||||
namespace com\fwdekker\deathnotifier\validator;
|
||||
|
||||
use com\fwdekker\deathnotifier\Response;
|
||||
use com\fwdekker\deathnotifier\ValidationException;
|
||||
|
||||
|
||||
/**
|
||||
* Requires the input to be a valid email address.
|
||||
* Verifies that the input is an email address.
|
||||
*/
|
||||
class IsEmailRule extends Rule
|
||||
{
|
||||
/**
|
||||
* Checks whether the input is a valid email address.
|
||||
* Verifies that the input is an email address.
|
||||
*
|
||||
* @param array<string, mixed> $inputs the list of inputs in which the value at `key` should be checked
|
||||
* @param string $key the key in `inputs` of the input to check
|
||||
* @return Response|null `null` if `$inputs[$key]` is an email address, or an unsatisfied `Response` otherwise
|
||||
* @return void if `$inputs[$key]` is an email address
|
||||
* @throws ValidationException if `$inputs[$key]` is not set or is not an email address
|
||||
*/
|
||||
public function check(array $inputs, string $key): ?Response
|
||||
public function check(array $inputs, string $key): void
|
||||
{
|
||||
return !isset($inputs[$key]) || !filter_var($inputs[$key], FILTER_VALIDATE_EMAIL)
|
||||
? Response::unsatisfied($this->override_message ?? "Enter a valid email address.", $key)
|
||||
: null;
|
||||
if (!isset($inputs[$key]) || !filter_var($inputs[$key], FILTER_VALIDATE_EMAIL))
|
||||
throw new ValidationException($this->override_message ?? "Enter a valid email address.", $key);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\validator;
|
||||
|
||||
use com\fwdekker\deathnotifier\ValidationException;
|
||||
|
||||
|
||||
/**
|
||||
* Verifies that the input has the specified value.
|
||||
*/
|
||||
class IsEqualToRule extends Rule
|
||||
{
|
||||
/**
|
||||
* @var string the required value
|
||||
*/
|
||||
private readonly string $expected;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs an `IsEqualToRule` that checks for equality with the specified value.
|
||||
*
|
||||
* @param string $expected the value that checked values should be equal to
|
||||
*/
|
||||
public function __construct(string $expected, ?string $override_message = null)
|
||||
{
|
||||
parent::__construct($override_message);
|
||||
|
||||
$this->expected = $expected;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verifies that the input has the specified value.
|
||||
*
|
||||
* @param array<string, mixed> $inputs the list of inputs in which the value at `key` should be checked
|
||||
* @param string $key the key in `inputs` of the input to check
|
||||
* @return void if `$inputs[$key]` equals `$expected`
|
||||
* @throws ValidationException if `$inputs[$key]` is not set or does not equal `$expected`
|
||||
*/
|
||||
public function check(array $inputs, string $key): void
|
||||
{
|
||||
if (!isset($inputs[$key]) || $inputs[$key] !== $this->expected)
|
||||
throw new ValidationException(
|
||||
$this->override_message ?? "Inputs '$key' should equal '$this->expected'.",
|
||||
$key
|
||||
);
|
||||
}
|
||||
}
|
|
@ -2,26 +2,25 @@
|
|||
|
||||
namespace com\fwdekker\deathnotifier\validator;
|
||||
|
||||
use com\fwdekker\deathnotifier\Response;
|
||||
use com\fwdekker\deathnotifier\ValidationException;
|
||||
|
||||
|
||||
/**
|
||||
* Requires the input to not be blank.
|
||||
* Verifies that the input is not blank.
|
||||
*/
|
||||
class IsNotBlankRule extends Rule
|
||||
{
|
||||
/**
|
||||
* Checks whether the input is not blank.
|
||||
* Verifies that the input is not blank.
|
||||
*
|
||||
* @param array<string, mixed> $inputs the list of inputs in which the value at `key` should be checked
|
||||
* @param string $key the key in `inputs` of the input to check
|
||||
* @return Response|null `null` if `trim($inputs[$key])` is not an empty string, or an unsatisfied `Response`
|
||||
* otherwise
|
||||
* @return void if `trim($inputs[$key])` is not an empty string
|
||||
* @throws ValidationException if `$inputs[$key]` is not set or if `trim($inputs[$key])` is an empty string
|
||||
*/
|
||||
public function check(array $inputs, string $key): ?Response
|
||||
public function check(array $inputs, string $key): void
|
||||
{
|
||||
return !isset($inputs[$key]) || trim($inputs[$key]) === ""
|
||||
? Response::unsatisfied($this->override_message ?? "Use at least one character.", $key)
|
||||
: null;
|
||||
if (!isset($inputs[$key]) || trim($inputs[$key]) === "")
|
||||
throw new ValidationException($this->override_message ?? "Use at least one character.", $key);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\validator;
|
||||
|
||||
use com\fwdekker\deathnotifier\ValidationException;
|
||||
|
||||
|
||||
/**
|
||||
* Verifies that the input is not set.
|
||||
*/
|
||||
class IsNotSetRule extends Rule
|
||||
{
|
||||
/**
|
||||
* Verifies that the input is not set.
|
||||
*
|
||||
* @param array<string, mixed> $inputs the list of inputs in which the value at `key` should be checked
|
||||
* @param string $key the key in `inputs` of the input to check
|
||||
* @return void if `$inputs[$key]` is not set
|
||||
* @throws ValidationException if `$inputs[$key]` is set
|
||||
*/
|
||||
public function check(array $inputs, string $key): void
|
||||
{
|
||||
if (isset($inputs[$key]))
|
||||
throw new ValidationException(
|
||||
$this->override_message ?? "Field '" . htmlentities($key) . "' must not be set.",
|
||||
$key
|
||||
);
|
||||
}
|
||||
}
|
|
@ -2,25 +2,28 @@
|
|||
|
||||
namespace com\fwdekker\deathnotifier\validator;
|
||||
|
||||
use com\fwdekker\deathnotifier\Response;
|
||||
use com\fwdekker\deathnotifier\ValidationException;
|
||||
|
||||
|
||||
/**
|
||||
* Requires the input to be set.
|
||||
* Verifies that the input is set.
|
||||
*/
|
||||
class IsSetRule extends Rule
|
||||
{
|
||||
/**
|
||||
* Checks whether the input is set.
|
||||
* Verifies that the input is set.
|
||||
*
|
||||
* @param array<string, mixed> $inputs the list of inputs in which the value at `key` should be checked
|
||||
* @param string $key the key in `inputs` of the input to check
|
||||
* @return Response|null `null` if `isset($inputs[$key])`, or an unsatisfied `Response` otherwise
|
||||
* @return void if `$inputs[$key]` is set
|
||||
* @throws ValidationException if `$inputs[$key]` is not set
|
||||
*/
|
||||
public function check(array $inputs, string $key): ?Response
|
||||
public function check(array $inputs, string $key): void
|
||||
{
|
||||
return !isset($inputs[$key])
|
||||
? Response::unsatisfied($this->override_message ?? "Field '" . htmlentities($key) . "' required.", $key)
|
||||
: null;
|
||||
if (!isset($inputs[$key]))
|
||||
throw new ValidationException(
|
||||
$this->override_message ?? "Field '" . htmlentities($key) . "' must be set.",
|
||||
$key
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace com\fwdekker\deathnotifier\validator;
|
||||
|
||||
use com\fwdekker\deathnotifier\Response;
|
||||
use com\fwdekker\deathnotifier\ValidationException;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -36,7 +37,8 @@ abstract class Rule
|
|||
*
|
||||
* @param array<string, mixed> $inputs the list of inputs in which the value at `key` should be checked
|
||||
* @param string $key the key in `inputs` of the input to check
|
||||
* @return Response|null `null` if the rule holds, or an unsatisfied `Response` otherwise
|
||||
* @return void if the rule holds
|
||||
* @throws ValidationException if the rule does not hold
|
||||
*/
|
||||
public abstract function check(array $inputs, string $key): ?Response;
|
||||
public abstract function check(array $inputs, string $key): void;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier;
|
||||
|
||||
use Exception;
|
||||
|
||||
|
||||
class ValidationException extends Exception
|
||||
{
|
||||
public readonly ?string $target;
|
||||
|
||||
|
||||
public function __construct(?string $message = null, ?string $target = null)
|
||||
{
|
||||
parent::__construct($message);
|
||||
|
||||
$this->target = $target;
|
||||
}
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\validator;
|
||||
|
||||
use com\fwdekker\deathnotifier\Response;
|
||||
|
||||
|
||||
/**
|
||||
* Validates arrays of inputs such as `$_POST` or `$_SESSION` using `Rule`s.
|
||||
*/
|
||||
class Validator
|
||||
{
|
||||
/**
|
||||
* Validates whether values in `inputs` match the rules specified in `rule_sets`.
|
||||
*
|
||||
* @param array<string, string> $inputs the array of inputs in which to check the values
|
||||
* @param array<string, Rule[]> $rule_sets maps keys in `inputs` to an array of `Rule`s to be checked
|
||||
* @return Response|null `null` if all rules are satisfied, or an unsatisfied `Response` otherwise
|
||||
*/
|
||||
static function validate_inputs(array $inputs, array $rule_sets): ?Response
|
||||
{
|
||||
foreach ($rule_sets as $key => $rules) {
|
||||
foreach ($rules as $rule) {
|
||||
$is_valid = $rule->check($inputs, $key);
|
||||
if ($is_valid !== null)
|
||||
return $is_valid;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validates that the user is logged in.
|
||||
*
|
||||
* @param array<string, string> $session the session to check
|
||||
* @return Response|null `null` if the user is logged in, or an unsatisfied `Response` otherwise
|
||||
*/
|
||||
static function validate_logged_in(array $session): ?Response
|
||||
{
|
||||
if (!isset($session["uuid"]))
|
||||
return Response::unsatisfied("You must be logged in to perform this action.");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the user is logged out.
|
||||
*
|
||||
* @param array<string, string> $session the session to check
|
||||
* @return Response|null `null` if the user is logged out, or an unsatisfied `Response` otherwise
|
||||
*/
|
||||
static function validate_logged_out(array $session): ?Response
|
||||
{
|
||||
if (isset($session["uuid"]))
|
||||
return Response::unsatisfied("You must be logged out to perform this action.");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the array contains the correct token.
|
||||
*
|
||||
* @param array<string, string> $token_array the array with key `token`
|
||||
* @param string $token the expected token
|
||||
* @return Response|null `null` if the token is correct, or an unsatisfied `Response` otherwise
|
||||
*/
|
||||
static function validate_token(array $token_array, string $token): ?Response
|
||||
{
|
||||
if (!isset($token_array["token"]) || $token_array["token"] !== $token)
|
||||
return Response::unsatisfied("Invalid request token. Please refresh the page and try again.");
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -4,13 +4,13 @@ namespace com\fwdekker\deathnotifier\validator;
|
|||
|
||||
|
||||
/**
|
||||
* Unit tests for `LengthRule`.
|
||||
* Unit tests for `HasLengthRule`.
|
||||
*/
|
||||
class LengthRuleTest extends RuleTest
|
||||
{
|
||||
function get_rule(?string $override = null): Rule
|
||||
{
|
||||
return new LengthRule(1, 6, $override);
|
||||
return new HasLengthRule(1, 6, $override);
|
||||
}
|
||||
|
||||
function get_valid_input(): ?string
|
||||
|
@ -26,7 +26,7 @@ class LengthRuleTest extends RuleTest
|
|||
|
||||
public function test_returns_null_if_input_is_exactly_minimum_length(): void
|
||||
{
|
||||
$rule = new LengthRule(1, 3);
|
||||
$rule = new HasLengthRule(1, 3);
|
||||
|
||||
$is_valid = $rule->check(["input" => "a"], "input");
|
||||
|
||||
|
@ -35,7 +35,7 @@ class LengthRuleTest extends RuleTest
|
|||
|
||||
public function test_returns_null_if_input_is_exactly_maximum_length(): void
|
||||
{
|
||||
$rule = new LengthRule(1, 3);
|
||||
$rule = new HasLengthRule(1, 3);
|
||||
|
||||
$is_valid = $rule->check(["input" => "123"], "input");
|
||||
|
||||
|
@ -44,7 +44,7 @@ class LengthRuleTest extends RuleTest
|
|||
|
||||
public function test_returns_null_if_input_is_strictly_inside_range(): void
|
||||
{
|
||||
$rule = new LengthRule(1, 3);
|
||||
$rule = new HasLengthRule(1, 3);
|
||||
|
||||
$is_valid = $rule->check(["input" => "12"], "input");
|
||||
|
||||
|
@ -53,7 +53,7 @@ class LengthRuleTest extends RuleTest
|
|||
|
||||
public function test_returns_not_null_if_input_is_strictly_below_minimum(): void
|
||||
{
|
||||
$rule = new LengthRule(1, 3);
|
||||
$rule = new HasLengthRule(1, 3);
|
||||
|
||||
$is_valid = $rule->check(["input" => ""], "input");
|
||||
|
||||
|
@ -62,7 +62,7 @@ class LengthRuleTest extends RuleTest
|
|||
|
||||
public function test_returns_not_null_if_input_is_strictly_above_maximum(): void
|
||||
{
|
||||
$rule = new LengthRule(1, 3);
|
||||
$rule = new HasLengthRule(1, 3);
|
||||
|
||||
$is_valid = $rule->check(["input" => "1234"], "input");
|
||||
|
||||
|
|
Loading…
Reference in New Issue