Make actions agnostic to the request method
And improve some exception structuring.
This commit is contained in:
parent
1cd9dfc9d2
commit
4c58b2a646
|
@ -60,6 +60,7 @@ $> mv dist/ /var/www/death-notifier/ # Move to public directory
|
||||||
|
|
||||||
Then, add the following lines to your crontab using `sudo -u www crontab -e`:
|
Then, add the following lines to your crontab using `sudo -u www crontab -e`:
|
||||||
```
|
```
|
||||||
* * * * * cd /var/www/death-notifier && php /var/www/death-notifier/api.php process-email-queue secret_password
|
* * * * * cd /var/www/death-notifier && php /var/www/death-notifier/api.php action=process-email-queue password=secret_password
|
||||||
*/5 * * * * cd /var/www/death-notifier && php /var/www/death-notifier/api.php update-all-trackings secret_password
|
*/5 * * * * cd /var/www/death-notifier && php /var/www/death-notifier/api.php action=update-all-trackings password=secret_password
|
||||||
```
|
```
|
||||||
|
It is recommended to also use a tool such as `newsyslog` to manage log rotation.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "fwdekker/death-notifier",
|
"name": "fwdekker/death-notifier",
|
||||||
"description": "Get notified when a famous person dies.",
|
"description": "Get notified when a famous person dies.",
|
||||||
"version": "0.16.1", "_comment_version": "Also update version in `package.json`!",
|
"version": "0.16.2", "_comment_version": "Also update version in `package.json`!",
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://git.fwdekker.com/tools/death-notifier",
|
"homepage": "https://git.fwdekker.com/tools/death-notifier",
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "death-notifier",
|
"name": "death-notifier",
|
||||||
"version": "0.16.1", "_comment_version": "Also update version in `composer.json`!",
|
"version": "0.16.2", "_comment_version": "Also update version in `composer.json`!",
|
||||||
"description": "Get notified when a famous person dies.",
|
"description": "Get notified when a famous person dies.",
|
||||||
"author": "Florine W. Dekker",
|
"author": "Florine W. Dekker",
|
||||||
"browser": "dist/bundle.js",
|
"browser": "dist/bundle.js",
|
||||||
|
|
114
src/main/api.php
114
src/main/api.php
|
@ -5,6 +5,7 @@ use com\fwdekker\deathnotifier\ActionMethod;
|
||||||
use com\fwdekker\deathnotifier\Config;
|
use com\fwdekker\deathnotifier\Config;
|
||||||
use com\fwdekker\deathnotifier\Database;
|
use com\fwdekker\deathnotifier\Database;
|
||||||
use com\fwdekker\deathnotifier\EmulateCronAction;
|
use com\fwdekker\deathnotifier\EmulateCronAction;
|
||||||
|
use com\fwdekker\deathnotifier\IllegalArgumentError;
|
||||||
use com\fwdekker\deathnotifier\LoggerUtil;
|
use com\fwdekker\deathnotifier\LoggerUtil;
|
||||||
use com\fwdekker\deathnotifier\mailer\MailManager;
|
use com\fwdekker\deathnotifier\mailer\MailManager;
|
||||||
use com\fwdekker\deathnotifier\mailer\ProcessEmailQueueAction;
|
use com\fwdekker\deathnotifier\mailer\ProcessEmailQueueAction;
|
||||||
|
@ -36,73 +37,88 @@ use com\fwdekker\deathnotifier\Util;
|
||||||
require_once __DIR__ . "/.vendor/autoload.php";
|
require_once __DIR__ . "/.vendor/autoload.php";
|
||||||
|
|
||||||
|
|
||||||
// Preamble
|
|
||||||
$logger = LoggerUtil::with_name();
|
$logger = LoggerUtil::with_name();
|
||||||
|
|
||||||
$config = Config::get();
|
|
||||||
if (hash_equals($config["admin"]["cli_secret"], "REPLACE THIS WITH A SECRET VALUE")) {
|
|
||||||
$logger->error("You must set a CLI secret in the configuration file before running Death Notifier.");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
$db = new Database($config["database"]["filename"]);
|
// Wrap everything in try-catch to always return *something* to user
|
||||||
$mediawiki = new MediaWiki();
|
|
||||||
$mail_manager = new MailManager($db->conn);
|
|
||||||
$user_manager = new UserManager($db->conn, $mail_manager);
|
|
||||||
$tracking_manager = new TrackingManager($db->conn);
|
|
||||||
|
|
||||||
|
|
||||||
// Handle request
|
|
||||||
try {
|
try {
|
||||||
session_start();
|
// Preamble
|
||||||
$_SESSION["token"] = $_SESSION["token"] ?? Util::generate_csrf_token();
|
$config = Config::get();
|
||||||
$_POST = Util::parse_post();
|
if (hash_equals($config["admin"]["cli_secret"], "REPLACE THIS WITH A SECRET VALUE"))
|
||||||
|
throw new IllegalArgumentError(
|
||||||
|
"You must set a CLI secret in the configuration file before running Death Notifier."
|
||||||
|
);
|
||||||
|
|
||||||
|
$db = new Database($config["database"]["filename"]);
|
||||||
|
|
||||||
|
$mediawiki = new MediaWiki();
|
||||||
|
$mail_manager = new MailManager($db->conn);
|
||||||
|
$user_manager = new UserManager($db->conn);
|
||||||
|
$tracking_manager = new TrackingManager($db->conn);
|
||||||
|
|
||||||
// Update database
|
|
||||||
$db->auto_install($mail_manager, $user_manager, $tracking_manager);
|
$db->auto_install($mail_manager, $user_manager, $tracking_manager);
|
||||||
$db->auto_migrate();
|
$db->auto_migrate();
|
||||||
|
|
||||||
// Dispatch request
|
session_start();
|
||||||
|
$_SESSION["token"] = $_SESSION["token"] ?? Util::generate_csrf_token();
|
||||||
|
|
||||||
|
|
||||||
|
// Set up request handlers
|
||||||
$dispatcher = new ActionDispatcher();
|
$dispatcher = new ActionDispatcher();
|
||||||
|
|
||||||
// GET actions
|
// GET actions
|
||||||
$dispatcher->register_action(new StartSessionAction($user_manager));
|
$dispatcher->register_actions([
|
||||||
$dispatcher->register_action(new GetPublicUserDataAction($user_manager));
|
[ActionMethod::GET, "start-session", new StartSessionAction($user_manager)],
|
||||||
$dispatcher->register_action(new ListTrackingsAction($tracking_manager));
|
[ActionMethod::GET, "get-user-data", new GetPublicUserDataAction($user_manager)],
|
||||||
$dispatcher->register_action(new ValidatePasswordResetTokenAction($user_manager));
|
[ActionMethod::GET, "list-trackings", new ListTrackingsAction($tracking_manager)],
|
||||||
|
|
||||||
|
[ActionMethod::GET, "validate-password-reset-token", new ValidatePasswordResetTokenAction($user_manager)],
|
||||||
|
]);
|
||||||
|
|
||||||
// POST actions
|
// POST actions
|
||||||
$dispatcher->register_action(new RegisterAction($db->conn, $user_manager, $mail_manager));
|
$dispatcher->register_actions([
|
||||||
$dispatcher->register_action(new LoginAction($user_manager));
|
[ActionMethod::POST, "register", new RegisterAction($db->conn, $user_manager, $mail_manager)],
|
||||||
$dispatcher->register_action(new LogoutAction());
|
[ActionMethod::POST, "login", new LoginAction($user_manager)],
|
||||||
$dispatcher->register_action(new ResendVerifyEmailAction($db->conn, $user_manager, $mail_manager));
|
[ActionMethod::POST, "logout", new LogoutAction()],
|
||||||
$dispatcher->register_action(new VerifyEmailAction($db->conn, $user_manager, $mail_manager));
|
[ActionMethod::POST, "user-delete", new UserDeleteAction($user_manager)],
|
||||||
$dispatcher->register_action(new ChangeEmailAction($db->conn, $user_manager, $mail_manager));
|
|
||||||
$dispatcher->register_action(new ToggleNotificationsAction($db->conn, $user_manager));
|
[ActionMethod::POST, "update-email", new ChangeEmailAction($db->conn, $user_manager, $mail_manager)],
|
||||||
$dispatcher->register_action(new ChangePasswordAction($db->conn, $user_manager, $mail_manager));
|
[ActionMethod::POST, "verify-email", new VerifyEmailAction($db->conn, $user_manager, $mail_manager)],
|
||||||
$dispatcher->register_action(new SendPasswordResetAction($db->conn, $user_manager, $mail_manager));
|
[ActionMethod::POST, "resend-verify-email", new ResendVerifyEmailAction($db->conn, $user_manager, $mail_manager)],
|
||||||
$dispatcher->register_action(new ResetPasswordAction($db->conn, $user_manager, $mail_manager));
|
[ActionMethod::POST, "toggle-notifications", new ToggleNotificationsAction($db->conn, $user_manager)],
|
||||||
$dispatcher->register_action(new UserDeleteAction($user_manager));
|
|
||||||
$dispatcher->register_action(new AddTrackingAction($tracking_manager, $mediawiki));
|
[ActionMethod::POST, "update-password", new ChangePasswordAction($db->conn, $user_manager, $mail_manager)],
|
||||||
$dispatcher->register_action(new RemoveTrackingAction($tracking_manager));
|
[ActionMethod::POST, "send-password-reset", new SendPasswordResetAction($db->conn, $user_manager, $mail_manager)],
|
||||||
|
[ActionMethod::POST, "reset-password", new ResetPasswordAction($db->conn, $user_manager, $mail_manager)],
|
||||||
|
|
||||||
|
[ActionMethod::POST, "add-tracking", new AddTrackingAction($tracking_manager, $mediawiki)],
|
||||||
|
[ActionMethod::POST, "remove-tracking", new RemoveTrackingAction($tracking_manager)],
|
||||||
|
]);
|
||||||
|
|
||||||
// CLI actions
|
// CLI actions
|
||||||
$cli_actions = [
|
$cli_actions = [
|
||||||
new UpdateTrackingsAction($db->conn, $tracking_manager, $mediawiki, $mail_manager),
|
new UpdateTrackingsAction($db->conn, $tracking_manager, $mediawiki, $mail_manager),
|
||||||
new ProcessEmailQueueAction($mail_manager),
|
new ProcessEmailQueueAction($mail_manager),
|
||||||
];
|
];
|
||||||
$dispatcher->register_action($cli_actions[0]);
|
$dispatcher->register_actions([
|
||||||
$dispatcher->register_action($cli_actions[1]);
|
[ActionMethod::CLI, "update-trackings", $cli_actions[0]],
|
||||||
$dispatcher->register_action(new EmulateCronAction($cli_actions));
|
[ActionMethod::CLI, "process-email-queue", $cli_actions[1]],
|
||||||
// Dispatch
|
[ActionMethod::CLI, "emulate-cron", new EmulateCronAction($cli_actions)],
|
||||||
if (isset($_GET["action"]))
|
]);
|
||||||
$response = $dispatcher->handle(ActionMethod::GET);
|
|
||||||
else if (isset($_POST["action"]))
|
|
||||||
$response = $dispatcher->handle(ActionMethod::POST);
|
// Handle request
|
||||||
else if ($argc > 1)
|
if (!isset($_SERVER["REQUEST_METHOD"]))
|
||||||
$response = $dispatcher->handle(ActionMethod::CLI);
|
$response = $dispatcher->handle(ActionMethod::CLI, Util::parse_cli());
|
||||||
|
else if ($_SERVER["REQUEST_METHOD"] === "GET")
|
||||||
|
$response = $dispatcher->handle(ActionMethod::GET, $_GET);
|
||||||
|
else if ($_SERVER["REQUEST_METHOD"] === "POST")
|
||||||
|
$response = $dispatcher->handle(ActionMethod::POST, Util::parse_post());
|
||||||
else
|
else
|
||||||
$response = Response::satisfied();
|
$response = Response::satisfied();
|
||||||
} catch (Exception $exception) {
|
} catch (Throwable $exception) {
|
||||||
$response = Response::unsatisfied("An unexpected error occurred. Please try again later.");
|
$response = Response::unsatisfied("An unhandled error occurred. Please try again later.");
|
||||||
$logger->error("An unexpected error occurred. Please try again later.", ["cause" => $exception]);
|
$logger->error("An unhandled error occurred. Please try again later.", ["cause" => $exception]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -577,11 +577,12 @@ doAfterLoad(() => {
|
||||||
addTrackingForm.addEventListener("submit", (event: SubmitEvent) => {
|
addTrackingForm.addEventListener("submit", (event: SubmitEvent) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
|
const inputName = $("#add-tracking-name").value;
|
||||||
postApi(
|
postApi(
|
||||||
{
|
{
|
||||||
action: "add-tracking",
|
action: "add-tracking",
|
||||||
token: csrfToken,
|
token: csrfToken,
|
||||||
person_name: $("#add-tracking-name").value,
|
person_name: inputName,
|
||||||
},
|
},
|
||||||
addTrackingForm,
|
addTrackingForm,
|
||||||
(response: ServerResponse) => {
|
(response: ServerResponse) => {
|
||||||
|
@ -590,12 +591,12 @@ doAfterLoad(() => {
|
||||||
|
|
||||||
showMessageSuccess(
|
showMessageSuccess(
|
||||||
addTrackingForm,
|
addTrackingForm,
|
||||||
response.payload["renamed"]
|
inputName.toLowerCase() !== response.payload["normalized_name"].toLowerCase()
|
||||||
? (
|
? (
|
||||||
`Successfully added <b>${response.payload["input"]}</b> as ` +
|
`Successfully added <b>${inputName}</b> as ` +
|
||||||
`<b>${response.payload["name"]}</b>!`
|
`<b>${response.payload["normalized_name"]}</b>!`
|
||||||
)
|
)
|
||||||
: `Successfully added <b>${response.payload["name"]}</b>!`
|
: `Successfully added <b>${response.payload["normalized_name"]}</b>!`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -6,7 +6,7 @@ use com\fwdekker\deathnotifier\validator\IsEqualToRule;
|
||||||
use com\fwdekker\deathnotifier\validator\IsNotSetRule;
|
use com\fwdekker\deathnotifier\validator\IsNotSetRule;
|
||||||
use com\fwdekker\deathnotifier\validator\IsSetRule;
|
use com\fwdekker\deathnotifier\validator\IsSetRule;
|
||||||
use com\fwdekker\deathnotifier\validator\Rule;
|
use com\fwdekker\deathnotifier\validator\Rule;
|
||||||
use com\fwdekker\deathnotifier\validator\ValidationException;
|
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,21 +32,10 @@ abstract class Action
|
||||||
*/
|
*/
|
||||||
private readonly array $rule_lists;
|
private readonly array $rule_lists;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var ActionMethod the method that this action handles
|
|
||||||
*/
|
|
||||||
public readonly ActionMethod $method;
|
|
||||||
/**
|
|
||||||
* @var string the name of the action that this action handles
|
|
||||||
*/
|
|
||||||
public readonly string $name;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new action.
|
* Constructs a new action.
|
||||||
*
|
*
|
||||||
* @param ActionMethod $method the method that this action handles
|
|
||||||
* @param string $name the name of the action that this action handles
|
|
||||||
* @param bool $require_logged_in `true` if and only if this action requires the user to be logged in
|
* @param bool $require_logged_in `true` if and only if this action requires the user to be logged in
|
||||||
* @param bool $require_logged_out `true` if and only if this action requires the user to be logged out
|
* @param bool $require_logged_out `true` if and only if this action requires the user to be logged out
|
||||||
* @param bool $require_valid_csrf_token `true` if and only if this action requires the request to have a valid CSRF
|
* @param bool $require_valid_csrf_token `true` if and only if this action requires the request to have a valid CSRF
|
||||||
|
@ -54,9 +43,7 @@ abstract class Action
|
||||||
* @param array<string, Rule[]> $rule_lists maps input keys to {@see Rule}s that should be validated before this
|
* @param array<string, Rule[]> $rule_lists maps input keys to {@see Rule}s that should be validated before this
|
||||||
* action is handled
|
* action is handled
|
||||||
*/
|
*/
|
||||||
public function __construct(ActionMethod $method,
|
public function __construct(bool $require_logged_in = false,
|
||||||
string $name,
|
|
||||||
bool $require_logged_in = false,
|
|
||||||
bool $require_logged_out = false,
|
bool $require_logged_out = false,
|
||||||
bool $require_valid_csrf_token = false,
|
bool $require_valid_csrf_token = false,
|
||||||
array $rule_lists = [])
|
array $rule_lists = [])
|
||||||
|
@ -64,9 +51,6 @@ abstract class Action
|
||||||
if ($require_logged_in && $require_logged_out)
|
if ($require_logged_in && $require_logged_out)
|
||||||
throw new InvalidArgumentException("Cannot require that user is both logged in and logged out.");
|
throw new InvalidArgumentException("Cannot require that user is both logged in and logged out.");
|
||||||
|
|
||||||
$this->method = $method;
|
|
||||||
$this->name = $name;
|
|
||||||
|
|
||||||
// TODO: Move authorisation-related validation to `dispatch` method?
|
// TODO: Move authorisation-related validation to `dispatch` method?
|
||||||
$this->require_logged_in = $require_logged_in;
|
$this->require_logged_in = $require_logged_in;
|
||||||
$this->require_logged_out = $require_logged_out;
|
$this->require_logged_out = $require_logged_out;
|
||||||
|
@ -78,13 +62,12 @@ abstract class Action
|
||||||
/**
|
/**
|
||||||
* Validates inputs according to `rule_lists`, throwing an exception if any input is invalid.
|
* Validates inputs according to `rule_lists`, throwing an exception if any input is invalid.
|
||||||
*
|
*
|
||||||
|
* @param array<int|string, mixed> $inputs the inputs to validate
|
||||||
* @return void if the input is valid
|
* @return void if the input is valid
|
||||||
* @throws ValidationException if the input is invalid
|
* @throws InvalidInputException if the input is invalid
|
||||||
*/
|
*/
|
||||||
function validate_inputs(): void
|
public function validate_inputs(array $inputs): void
|
||||||
{
|
{
|
||||||
$inputs = $this->method->get_inputs();
|
|
||||||
|
|
||||||
if ($this->require_logged_in)
|
if ($this->require_logged_in)
|
||||||
(new IsSetRule("You must be logged in to perform this action."))->check($_SESSION, "uuid");
|
(new IsSetRule("You must be logged in to perform this action."))->check($_SESSION, "uuid");
|
||||||
if ($this->require_logged_out)
|
if ($this->require_logged_out)
|
||||||
|
@ -101,9 +84,10 @@ abstract class Action
|
||||||
/**
|
/**
|
||||||
* Performs the action.
|
* Performs the action.
|
||||||
*
|
*
|
||||||
|
* @param array<int|string, mixed> $inputs the inputs to perform the action with
|
||||||
* @return mixed the data requested by the action; may be `null`
|
* @return mixed the data requested by the action; may be `null`
|
||||||
* @throws ActionException if the action could not be performed
|
* @throws ActionException if the action could not be performed
|
||||||
* @throws ValidationException if the inputs are invalid upon further inspection
|
* @throws InvalidInputException if the inputs are invalid upon further inspection
|
||||||
*/
|
*/
|
||||||
abstract function handle(): mixed;
|
abstract function handle(array $inputs): mixed;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
namespace com\fwdekker\deathnotifier;
|
namespace com\fwdekker\deathnotifier;
|
||||||
|
|
||||||
use com\fwdekker\deathnotifier\validator\ValidationException;
|
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,46 +23,61 @@ class ActionDispatcher
|
||||||
* Only one action can be registered given a combination of method and action name. An exception is thrown if
|
* Only one action can be registered given a combination of method and action name. An exception is thrown if
|
||||||
* this restriction is violated, because that is likely to be a mistake.
|
* this restriction is violated, because that is likely to be a mistake.
|
||||||
*
|
*
|
||||||
|
* @param ActionMethod $method the request method that this action should handle
|
||||||
|
* @param string $action_name the action name that this action should handle
|
||||||
* @param Action $action the action to register
|
* @param Action $action the action to register
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function register_action(Action $action): void
|
public function register_action(ActionMethod $method, string $action_name, Action $action): void
|
||||||
{
|
{
|
||||||
$method = $action->method->name;
|
$method_name = $method->name;
|
||||||
|
|
||||||
if (!isset($this->actions[$method]))
|
if (!isset($this->actions[$method_name]))
|
||||||
$this->actions[$method] = [];
|
$this->actions[$method_name] = [];
|
||||||
|
|
||||||
if (isset($this->actions[$method][$action->name]))
|
if (isset($this->actions[$method_name][$action_name]))
|
||||||
// TODO: Throw more specific exceptions(?)
|
throw new IllegalArgumentError("Cannot register another handler for $method_name action '$action_name'.");
|
||||||
throw new InvalidArgumentException("Cannot register another handler for $method action '$action->name'.");
|
|
||||||
|
|
||||||
$this->actions[$method][$action->name] = $action;
|
$this->actions[$method_name][$action_name] = $action;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility method that invokes `#register_action` for each of the given `actions`.
|
||||||
|
*
|
||||||
|
* @param array<array{ActionMethod, string, Action}> $actions the actions to register, mapped from the request
|
||||||
|
* method and action name that the action should handle
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function register_actions(array $actions): void
|
||||||
|
{
|
||||||
|
foreach ($actions as $action)
|
||||||
|
$this->register_action($action[0], $action[1], $action[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes the registered action for the given pair of method and action name.
|
* Executes the registered action for the given pair of method and action name.
|
||||||
*
|
*
|
||||||
* @param ActionMethod $method the method of the action to execute
|
* @param ActionMethod $method the method of the action to execute
|
||||||
|
* @param array<int|string, mixed> $inputs the inputs to the action
|
||||||
* @return Response a satisfied response with the action's output if the action did not throw an exception, or an
|
* @return Response a satisfied response with the action's output if the action did not throw an exception, or an
|
||||||
* unsatisfied response with the exception's message and target
|
* unsatisfied response with the exception's message and target
|
||||||
*/
|
*/
|
||||||
public function handle(ActionMethod $method): Response
|
public function handle(ActionMethod $method, array $inputs): Response
|
||||||
{
|
{
|
||||||
$inputs = $method->get_inputs();
|
|
||||||
if (!isset($inputs["action"]))
|
if (!isset($inputs["action"]))
|
||||||
throw new InvalidArgumentException("No action specified.");
|
throw new InvalidArgumentException("Malformed request: No action specified.");
|
||||||
|
|
||||||
|
$method_name = $method->name;
|
||||||
$action_name = $inputs["action"];
|
$action_name = $inputs["action"];
|
||||||
if (!isset($this->actions[$method->name]) || !isset($this->actions[$method->name][$action_name]))
|
if (!isset($this->actions[$method_name]) || !isset($this->actions[$method_name][$action_name]))
|
||||||
throw new InvalidArgumentException("No handler for $method->name action '$action_name'.");
|
throw new InvalidArgumentException("Malformed request: Unknown $method_name action '$action_name'.");
|
||||||
|
|
||||||
$action = $this->actions[$method->name][$action_name];
|
$action = $this->actions[$method_name][$action_name];
|
||||||
try {
|
try {
|
||||||
$action->validate_inputs();
|
$action->validate_inputs($inputs);
|
||||||
$payload = $action->handle();
|
$payload = $action->handle($inputs);
|
||||||
return Response::satisfied($payload);
|
return Response::satisfied($payload);
|
||||||
} catch (ActionException|ValidationException $exception) {
|
} catch (ActionException|InvalidInputException $exception) {
|
||||||
return Response::unsatisfied($exception->getMessage(), $exception->target);
|
return Response::unsatisfied($exception->getMessage(), $exception->target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,19 +20,4 @@ enum ActionMethod
|
||||||
* HTTP POST request.
|
* HTTP POST request.
|
||||||
*/
|
*/
|
||||||
case POST;
|
case POST;
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the user's inputs corresponding to this request method.
|
|
||||||
*
|
|
||||||
* @return array<int|string, mixed> the user's inputs corresponding to this request method
|
|
||||||
*/
|
|
||||||
function get_inputs(): array
|
|
||||||
{
|
|
||||||
return match ($this) {
|
|
||||||
ActionMethod::CLI => Util::parse_cli(),
|
|
||||||
ActionMethod::GET => $_GET,
|
|
||||||
ActionMethod::POST => $_POST,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ use InvalidArgumentException;
|
||||||
*
|
*
|
||||||
* Contains global state, but that's fine since it's read-only.
|
* Contains global state, but that's fine since it's read-only.
|
||||||
*/
|
*/
|
||||||
|
// TODO: Override dynamically from tests
|
||||||
class Config
|
class Config
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -63,7 +63,7 @@ class Database
|
||||||
if ($stmt->fetch()[0] !== 0)
|
if ($stmt->fetch()[0] !== 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
$this->logger->info("Database does not exist. Installing new database.");
|
$this->logger->notice("Database does not exist. Installing new database.");
|
||||||
|
|
||||||
// Create `meta` table
|
// Create `meta` table
|
||||||
$this->conn->exec("CREATE TABLE meta(k TEXT NOT NULL UNIQUE PRIMARY KEY, v TEXT);");
|
$this->conn->exec("CREATE TABLE meta(k TEXT NOT NULL UNIQUE PRIMARY KEY, v TEXT);");
|
||||||
|
@ -76,7 +76,7 @@ class Database
|
||||||
$user_manager->install();
|
$user_manager->install();
|
||||||
$tracking_manager->install();
|
$tracking_manager->install();
|
||||||
|
|
||||||
$this->logger->info("Installation complete.");
|
$this->logger->notice("Installation complete.");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ class Database
|
||||||
if (Comparator::greaterThanOrEqualTo($db_version, Database::LATEST_VERSION))
|
if (Comparator::greaterThanOrEqualTo($db_version, Database::LATEST_VERSION))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
$this->logger->info("Current db is v$db_version. Will migrate to v" . Database::LATEST_VERSION . ".");
|
$this->logger->notice("Current db is v$db_version. Will migrate to v" . Database::LATEST_VERSION . ".");
|
||||||
|
|
||||||
// Get current version
|
// Get current version
|
||||||
$stmt = $this->conn->prepare("SELECT v FROM meta WHERE k='version';");
|
$stmt = $this->conn->prepare("SELECT v FROM meta WHERE k='version';");
|
||||||
|
@ -113,7 +113,7 @@ class Database
|
||||||
$stmt->bindValue(":version", Database::LATEST_VERSION);
|
$stmt->bindValue(":version", Database::LATEST_VERSION);
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
|
|
||||||
$this->logger->info("Completed migration to v" . Database::LATEST_VERSION . ".");
|
$this->logger->notice("Completed migration to v" . Database::LATEST_VERSION . ".");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,7 +125,7 @@ class Database
|
||||||
*/
|
*/
|
||||||
private function migrate_0_5_0(): void
|
private function migrate_0_5_0(): void
|
||||||
{
|
{
|
||||||
$this->logger->info("Migrating to v0.5.0.");
|
$this->logger->notice("Migrating to v0.5.0.");
|
||||||
|
|
||||||
$this->conn->exec("ALTER TABLE users
|
$this->conn->exec("ALTER TABLE users
|
||||||
ADD COLUMN email_notifications_enabled INT NOT NULL DEFAULT(1);");
|
ADD COLUMN email_notifications_enabled INT NOT NULL DEFAULT(1);");
|
||||||
|
@ -139,7 +139,7 @@ class Database
|
||||||
*/
|
*/
|
||||||
private function migrate_0_8_0(): void
|
private function migrate_0_8_0(): void
|
||||||
{
|
{
|
||||||
$this->logger->info("Migrating to v0.8.0.");
|
$this->logger->notice("Migrating to v0.8.0.");
|
||||||
|
|
||||||
$this->conn->exec("CREATE TABLE new_email_tasks(type TEXT NOT NULL,
|
$this->conn->exec("CREATE TABLE new_email_tasks(type TEXT NOT NULL,
|
||||||
recipient TEXT NOT NULL,
|
recipient TEXT NOT NULL,
|
||||||
|
@ -160,7 +160,7 @@ class Database
|
||||||
*/
|
*/
|
||||||
private function migrate_0_10_0(): void
|
private function migrate_0_10_0(): void
|
||||||
{
|
{
|
||||||
$this->logger->info("Migrating to v0.10.0.");
|
$this->logger->notice("Migrating to v0.10.0.");
|
||||||
|
|
||||||
$this->conn->exec("CREATE TABLE new_email_tasks(type TEXT NOT NULL,
|
$this->conn->exec("CREATE TABLE new_email_tasks(type TEXT NOT NULL,
|
||||||
recipient TEXT NOT NULL,
|
recipient TEXT NOT NULL,
|
||||||
|
@ -185,7 +185,7 @@ class Database
|
||||||
*/
|
*/
|
||||||
private function migrate_0_16_0(): void
|
private function migrate_0_16_0(): void
|
||||||
{
|
{
|
||||||
$this->logger->info("Migrating to v0.16.0.");
|
$this->logger->notice("Migrating to v0.16.0.");
|
||||||
|
|
||||||
$this->conn->exec("DROP TABLE email_tasks;");
|
$this->conn->exec("DROP TABLE email_tasks;");
|
||||||
$this->conn->exec("CREATE TABLE email_tasks(type_key TEXT NOT NULL,
|
$this->conn->exec("CREATE TABLE email_tasks(type_key TEXT NOT NULL,
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
namespace com\fwdekker\deathnotifier;
|
namespace com\fwdekker\deathnotifier;
|
||||||
|
|
||||||
use com\fwdekker\deathnotifier\validator\ValidationException;
|
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -30,7 +30,7 @@ class EmulateCronAction extends Action
|
||||||
*/
|
*/
|
||||||
public function __construct(array $actions)
|
public function __construct(array $actions)
|
||||||
{
|
{
|
||||||
parent::__construct(ActionMethod::CLI, "emulate-cron");
|
parent::__construct();
|
||||||
|
|
||||||
$this->actions = $actions;
|
$this->actions = $actions;
|
||||||
}
|
}
|
||||||
|
@ -38,27 +38,29 @@ class EmulateCronAction extends Action
|
||||||
/**
|
/**
|
||||||
* Validates the inputs of each registered action.
|
* Validates the inputs of each registered action.
|
||||||
*
|
*
|
||||||
|
* @param array<int|string, mixed> $inputs the inputs to validate
|
||||||
* @return void if the input is valid
|
* @return void if the input is valid
|
||||||
* @throws ValidationException if the input is invalid
|
* @throws InvalidInputException if the input is invalid
|
||||||
*/
|
*/
|
||||||
public function validate_inputs(): void
|
public function validate_inputs(array $inputs): void
|
||||||
{
|
{
|
||||||
foreach ($this->actions as $action)
|
foreach ($this->actions as $action)
|
||||||
$action->validate_inputs();
|
$action->validate_inputs($inputs);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates all trackings and processes the mail queue at a regular interval.
|
* Updates all trackings and processes the mail queue at a regular interval.
|
||||||
*
|
*
|
||||||
|
* @param array<int|string, mixed> $inputs ignored
|
||||||
* @return never
|
* @return never
|
||||||
*/
|
*/
|
||||||
public function handle(): never
|
public function handle(array $inputs): never
|
||||||
{
|
{
|
||||||
// @phpstan-ignore-next-line
|
// @phpstan-ignore-next-line
|
||||||
while (true) {
|
while (true) {
|
||||||
print("Emulating cron jobs.\n");
|
print("Emulating cron jobs.\n");
|
||||||
foreach ($this->actions as $action)
|
foreach ($this->actions as $action)
|
||||||
$action->handle();
|
$action->handle($inputs);
|
||||||
print("Done.\n");
|
print("Done.\n");
|
||||||
|
|
||||||
sleep(self::INTERVAL);
|
sleep(self::INTERVAL);
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace com\fwdekker\deathnotifier;
|
||||||
|
|
||||||
|
use Error;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown if a function receives an argument which it cannot handle.
|
||||||
|
*
|
||||||
|
* This is an error, so it always indicates a bug in the program.
|
||||||
|
*/
|
||||||
|
class IllegalArgumentError extends Error
|
||||||
|
{
|
||||||
|
// Intentionally left empty
|
||||||
|
}
|
|
@ -6,9 +6,11 @@ use Error;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thrown if something happens that should not be able to happen, and there is no way to recover.
|
* Thrown if something happens that should not be able to happen.
|
||||||
|
*
|
||||||
|
* This is an error, so it always indicates a bug in the program.
|
||||||
*/
|
*/
|
||||||
class IllegalStateException extends Error
|
class IllegalStateError extends Error
|
||||||
{
|
{
|
||||||
// Intentionally left empty
|
// Intentionally left empty
|
||||||
}
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace com\fwdekker\deathnotifier;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown to indicate that a request to the server was malformed in some way.
|
||||||
|
*
|
||||||
|
* This is an exception, not an error, so it indicates that the client that sent the request did something wrong.
|
||||||
|
*/
|
||||||
|
class MalformedRequestException extends Exception
|
||||||
|
{
|
||||||
|
// Intentionally left empty
|
||||||
|
}
|
|
@ -24,7 +24,7 @@ class StartSessionAction extends Action
|
||||||
*/
|
*/
|
||||||
public function __construct(UserManager $user_manager)
|
public function __construct(UserManager $user_manager)
|
||||||
{
|
{
|
||||||
parent::__construct(ActionMethod::GET, "start-session");
|
parent::__construct();
|
||||||
|
|
||||||
$this->user_manager = $user_manager;
|
$this->user_manager = $user_manager;
|
||||||
}
|
}
|
||||||
|
@ -33,11 +33,12 @@ class StartSessionAction extends Action
|
||||||
/**
|
/**
|
||||||
* Starts a new user session, or continues an existing one.
|
* Starts a new user session, or continues an existing one.
|
||||||
*
|
*
|
||||||
|
* @param array<int|string, mixed> $inputs ignored
|
||||||
* @return array{"logged_in": bool, "global_message"?: string} whether the user is logged in, and the message to be
|
* @return array{"logged_in": bool, "global_message"?: string} whether the user is logged in, and the message to be
|
||||||
* displayed at the top of the page, if any
|
* displayed at the top of the page, if any
|
||||||
* @throws ActionException if no CSRF token could be generated
|
* @throws ActionException if no CSRF token could be generated
|
||||||
*/
|
*/
|
||||||
function handle(): array
|
function handle(array $inputs): array
|
||||||
{
|
{
|
||||||
$config = Config::get();
|
$config = Config::get();
|
||||||
$payload = [];
|
$payload = [];
|
||||||
|
|
|
@ -2,7 +2,9 @@
|
||||||
|
|
||||||
namespace com\fwdekker\deathnotifier;
|
namespace com\fwdekker\deathnotifier;
|
||||||
|
|
||||||
|
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||||
use Exception;
|
use Exception;
|
||||||
|
use JsonException;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -13,17 +15,24 @@ class Util
|
||||||
/**
|
/**
|
||||||
* Parses POST values from JSON-based inputs.
|
* Parses POST values from JSON-based inputs.
|
||||||
*
|
*
|
||||||
* @return array<int|string, mixed>|null the parsed POST-ed values
|
* @return array<int|string, mixed> the parsed POSTed values, or an empty array if no values were POSTed
|
||||||
|
* @throws InvalidInputException if there are no POST input values, or if the POST input is not valid JSON
|
||||||
*/
|
*/
|
||||||
static function parse_post(): ?array
|
static function parse_post(): array
|
||||||
{
|
{
|
||||||
$output = $_POST;
|
|
||||||
|
|
||||||
$post_input = file_get_contents("php://input");
|
$post_input = file_get_contents("php://input");
|
||||||
if ($post_input !== false)
|
if ($post_input === false || trim($post_input) === "")
|
||||||
$output = json_decode($post_input, associative: true);
|
return [];
|
||||||
|
|
||||||
return $output;
|
try {
|
||||||
|
$post = json_decode($post_input, associative: true, flags: JSON_THROW_ON_ERROR);
|
||||||
|
if ($post === null)
|
||||||
|
throw new InvalidInputException("Malformed request: POST data is `null`.");
|
||||||
|
|
||||||
|
return $post;
|
||||||
|
} catch (JsonException) {
|
||||||
|
throw new InvalidInputException("Malformed request: POST data could not be parsed as JSON.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -31,12 +40,13 @@ class Util
|
||||||
*
|
*
|
||||||
* Code from https://www.php.net/manual/en/features.commandline.php#108883.
|
* Code from https://www.php.net/manual/en/features.commandline.php#108883.
|
||||||
*
|
*
|
||||||
* @return array<int|string, mixed> the parsed CLI inputs
|
* @return array<int|string, mixed> the parsed CLI inputs, or an empty array if no values were passed on the CLI
|
||||||
*/
|
*/
|
||||||
static function parse_cli(): array
|
static function parse_cli(): array
|
||||||
{
|
{
|
||||||
parse_str(implode("&", array_slice($_SERVER["argv"], 1)), $output);
|
if (!isset($_SERVER["argc"]) || $_SERVER["argc"] <= 1) return [];
|
||||||
|
|
||||||
|
parse_str(implode("&", array_slice($_SERVER["argv"], 1)), $output);
|
||||||
return $output;
|
return $output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ namespace com\fwdekker\deathnotifier\mailer;
|
||||||
|
|
||||||
use com\fwdekker\deathnotifier\Action;
|
use com\fwdekker\deathnotifier\Action;
|
||||||
use com\fwdekker\deathnotifier\ActionException;
|
use com\fwdekker\deathnotifier\ActionException;
|
||||||
use com\fwdekker\deathnotifier\ActionMethod;
|
|
||||||
use com\fwdekker\deathnotifier\Config;
|
use com\fwdekker\deathnotifier\Config;
|
||||||
use com\fwdekker\deathnotifier\LoggerUtil;
|
use com\fwdekker\deathnotifier\LoggerUtil;
|
||||||
use com\fwdekker\deathnotifier\validator\IsEqualToRule;
|
use com\fwdekker\deathnotifier\validator\IsEqualToRule;
|
||||||
|
@ -37,8 +36,6 @@ class ProcessEmailQueueAction extends Action
|
||||||
public function __construct(MailManager $mail_manager)
|
public function __construct(MailManager $mail_manager)
|
||||||
{
|
{
|
||||||
parent::__construct(
|
parent::__construct(
|
||||||
ActionMethod::CLI,
|
|
||||||
"process-email-queue",
|
|
||||||
rule_lists: [
|
rule_lists: [
|
||||||
"password" => [new IsEqualToRule(Config::get()["admin"]["cli_secret"], "Incorrect password.")]
|
"password" => [new IsEqualToRule(Config::get()["admin"]["cli_secret"], "Incorrect password.")]
|
||||||
],
|
],
|
||||||
|
@ -52,10 +49,11 @@ class ProcessEmailQueueAction extends Action
|
||||||
/**
|
/**
|
||||||
* Processes the queue.
|
* Processes the queue.
|
||||||
*
|
*
|
||||||
|
* @param array<int|string, mixed> $inputs ignored
|
||||||
* @return null
|
* @return null
|
||||||
* @throws ActionException if the mailer could not be created or if an email could not be sent
|
* @throws ActionException if the mailer could not be created or if an email could not be sent
|
||||||
*/
|
*/
|
||||||
public function handle(): mixed
|
public function handle(array $inputs): mixed
|
||||||
{
|
{
|
||||||
$emails = $this->mail_manager->get_queue();
|
$emails = $this->mail_manager->get_queue();
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ namespace com\fwdekker\deathnotifier\mediawiki;
|
||||||
|
|
||||||
use com\fwdekker\deathnotifier\LoggerUtil;
|
use com\fwdekker\deathnotifier\LoggerUtil;
|
||||||
use com\fwdekker\deathnotifier\validator\Rule;
|
use com\fwdekker\deathnotifier\validator\Rule;
|
||||||
use com\fwdekker\deathnotifier\validator\ValidationException;
|
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||||
use Monolog\Logger;
|
use Monolog\Logger;
|
||||||
|
|
||||||
|
|
||||||
|
@ -45,18 +45,18 @@ class IsPersonPageRule extends Rule
|
||||||
* @param array<int|string, mixed> $inputs the list of inputs in which the value at `key` should be checked
|
* @param array<int|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
|
* @param string $key the key in `inputs` of the input to check
|
||||||
* @return void if `$inputs[$key]` refers to a page about a person on Wikipedia
|
* @return void if `$inputs[$key]` refers to a page about a person on Wikipedia
|
||||||
* @throws ValidationException if `$inputs[$key]` is not set or does not refer to a page about a person on Wikipedia
|
* @throws InvalidInputException if `$inputs[$key]` is not set or does not refer to a page about a person on Wikipedia
|
||||||
*/
|
*/
|
||||||
public function check(array $inputs, string $key): void
|
public function check(array $inputs, string $key): void
|
||||||
{
|
{
|
||||||
if (!isset($inputs[$key])) throw new ValidationException($this->override_message ?? "Field must be set.", $key);
|
if (!isset($inputs[$key])) throw new InvalidInputException($this->override_message ?? "Field must be set.", $key);
|
||||||
|
|
||||||
$person_name = $inputs[$key];
|
$person_name = $inputs[$key];
|
||||||
try {
|
try {
|
||||||
$info = $this->mediawiki->query_person_info([$person_name]);
|
$info = $this->mediawiki->query_person_info([$person_name]);
|
||||||
} catch (MediaWikiException $exception) {
|
} catch (MediaWikiException $exception) {
|
||||||
$this->logger->error("Failed to query page info.", ["cause" => $exception, "name" => $person_name]);
|
$this->logger->error("Failed to query page info.", ["cause" => $exception, "name" => $person_name]);
|
||||||
throw new ValidationException(
|
throw new InvalidInputException(
|
||||||
$this->override_message ?? "Could not reach Wikipedia. Maybe the website is down?"
|
$this->override_message ?? "Could not reach Wikipedia. Maybe the website is down?"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ class IsPersonPageRule extends Rule
|
||||||
$type = $info->results[$normalized_name]["type"];
|
$type = $info->results[$normalized_name]["type"];
|
||||||
|
|
||||||
if (in_array($normalized_name, $info->missing))
|
if (in_array($normalized_name, $info->missing))
|
||||||
throw new ValidationException(
|
throw new InvalidInputException(
|
||||||
$this->override_message ??
|
$this->override_message ??
|
||||||
"Wikipedia does not have an article about " .
|
"Wikipedia does not have an article about " .
|
||||||
"<b><a href='https://en.wikipedia.org/wiki/Special:Search?search=" .
|
"<b><a href='https://en.wikipedia.org/wiki/Special:Search?search=" .
|
||||||
|
@ -75,7 +75,7 @@ class IsPersonPageRule extends Rule
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($type === ArticleType::Disambiguation)
|
if ($type === ArticleType::Disambiguation)
|
||||||
throw new ValidationException(
|
throw new InvalidInputException(
|
||||||
$this->override_message ??
|
$this->override_message ??
|
||||||
"<b><a href='https://en.wikipedia.org/wiki/" . rawurlencode($normalized_name) . "'>" .
|
"<b><a href='https://en.wikipedia.org/wiki/" . rawurlencode($normalized_name) . "'>" .
|
||||||
htmlentities($normalized_name) . "</a></b> refers to multiple articles. " .
|
htmlentities($normalized_name) . "</a></b> refers to multiple articles. " .
|
||||||
|
@ -85,7 +85,7 @@ class IsPersonPageRule extends Rule
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($type === ArticleType::Other)
|
if ($type === ArticleType::Other)
|
||||||
throw new ValidationException(
|
throw new InvalidInputException(
|
||||||
$this->override_message ??
|
$this->override_message ??
|
||||||
"The Wikipedia article about " .
|
"The Wikipedia article about " .
|
||||||
"<b><a href='https://en.wikipedia.org/wiki/" . rawurlencode($normalized_name) . "'>" .
|
"<b><a href='https://en.wikipedia.org/wiki/" . rawurlencode($normalized_name) . "'>" .
|
||||||
|
|
|
@ -4,15 +4,14 @@ namespace com\fwdekker\deathnotifier\tracking;
|
||||||
|
|
||||||
use com\fwdekker\deathnotifier\Action;
|
use com\fwdekker\deathnotifier\Action;
|
||||||
use com\fwdekker\deathnotifier\ActionException;
|
use com\fwdekker\deathnotifier\ActionException;
|
||||||
use com\fwdekker\deathnotifier\ActionMethod;
|
use com\fwdekker\deathnotifier\IllegalStateError;
|
||||||
use com\fwdekker\deathnotifier\IllegalStateException;
|
|
||||||
use com\fwdekker\deathnotifier\LoggerUtil;
|
use com\fwdekker\deathnotifier\LoggerUtil;
|
||||||
use com\fwdekker\deathnotifier\mediawiki\IsPersonPageRule;
|
use com\fwdekker\deathnotifier\mediawiki\IsPersonPageRule;
|
||||||
use com\fwdekker\deathnotifier\mediawiki\MediaWiki;
|
use com\fwdekker\deathnotifier\mediawiki\MediaWiki;
|
||||||
use com\fwdekker\deathnotifier\mediawiki\MediaWikiException;
|
use com\fwdekker\deathnotifier\mediawiki\MediaWikiException;
|
||||||
use com\fwdekker\deathnotifier\validator\HasLengthRule;
|
use com\fwdekker\deathnotifier\validator\HasLengthRule;
|
||||||
use com\fwdekker\deathnotifier\validator\IsNotBlankRule;
|
use com\fwdekker\deathnotifier\validator\IsNotBlankRule;
|
||||||
use com\fwdekker\deathnotifier\validator\ValidationException;
|
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||||
use Monolog\Logger;
|
use Monolog\Logger;
|
||||||
|
|
||||||
|
|
||||||
|
@ -44,8 +43,6 @@ class AddTrackingAction extends Action
|
||||||
public function __construct(TrackingManager $tracking_manager, MediaWiki $mediawiki)
|
public function __construct(TrackingManager $tracking_manager, MediaWiki $mediawiki)
|
||||||
{
|
{
|
||||||
parent::__construct(
|
parent::__construct(
|
||||||
ActionMethod::POST,
|
|
||||||
"add-tracking",
|
|
||||||
require_logged_in: true,
|
require_logged_in: true,
|
||||||
require_valid_csrf_token: true,
|
require_valid_csrf_token: true,
|
||||||
rule_lists: [
|
rule_lists: [
|
||||||
|
@ -64,16 +61,18 @@ class AddTrackingAction extends Action
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a tracking by the current user of `$_POST["person_name"]`.
|
* Adds a tracking by the current user of the specified person.
|
||||||
*
|
*
|
||||||
* @return array{"name": string, "input": string, "renamed": bool} the
|
* @param array<int|string, mixed> $inputs `"person_name": string`: the name of the person to track
|
||||||
|
* @return array{"input_name": string, "normalized_name": string} the person's name as input by the user, and the
|
||||||
|
* normalized version of that name
|
||||||
* @throws ActionException if the Wikipedia API could not be reached
|
* @throws ActionException if the Wikipedia API could not be reached
|
||||||
* @throws ValidationException if the user is already tracking this person
|
* @throws InvalidInputException if the user is already tracking this person
|
||||||
*/
|
*/
|
||||||
function handle(): array
|
public function handle(array $inputs): array
|
||||||
{
|
{
|
||||||
$user_uuid = $_SESSION["uuid"];
|
$user_uuid = $_SESSION["uuid"];
|
||||||
$person_name = strval($_POST["person_name"]);
|
$person_name = strval($inputs["person_name"]);
|
||||||
|
|
||||||
// Query API
|
// Query API
|
||||||
try {
|
try {
|
||||||
|
@ -86,19 +85,18 @@ class AddTrackingAction extends Action
|
||||||
$normalized_name = $info->redirects[$person_name];
|
$normalized_name = $info->redirects[$person_name];
|
||||||
$status = $info->results[$normalized_name]["status"];
|
$status = $info->results[$normalized_name]["status"];
|
||||||
if ($status === null)
|
if ($status === null)
|
||||||
throw new IllegalStateException("Article is about person, but person has no status.");
|
throw new IllegalStateError("Article is about person, but person has no status.");
|
||||||
|
|
||||||
// Add tracking (Transaction is not necessary)
|
// Add tracking (Transaction is not necessary)
|
||||||
if ($this->tracking_manager->has_tracking($user_uuid, $normalized_name))
|
if ($this->tracking_manager->has_tracking($user_uuid, $normalized_name))
|
||||||
throw new ValidationException("You are already tracking <b>$normalized_name</b>.");
|
throw new InvalidInputException("You are already tracking <b>$normalized_name</b>.");
|
||||||
|
|
||||||
$this->tracking_manager->add_tracking($user_uuid, $normalized_name, $status);
|
$this->tracking_manager->add_tracking($user_uuid, $normalized_name, $status);
|
||||||
|
|
||||||
// Respond
|
// Respond
|
||||||
return [
|
return [
|
||||||
"name" => $normalized_name,
|
"input_name" => $person_name,
|
||||||
"input" => $person_name,
|
"normalized_name" => $normalized_name,
|
||||||
"renamed" => strtolower($person_name) !== strtolower($normalized_name)
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
namespace com\fwdekker\deathnotifier\tracking;
|
namespace com\fwdekker\deathnotifier\tracking;
|
||||||
|
|
||||||
use com\fwdekker\deathnotifier\Action;
|
use com\fwdekker\deathnotifier\Action;
|
||||||
use com\fwdekker\deathnotifier\ActionMethod;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -25,8 +24,6 @@ class ListTrackingsAction extends Action
|
||||||
public function __construct(TrackingManager $tracking_manager)
|
public function __construct(TrackingManager $tracking_manager)
|
||||||
{
|
{
|
||||||
parent::__construct(
|
parent::__construct(
|
||||||
ActionMethod::GET,
|
|
||||||
"list-trackings",
|
|
||||||
require_logged_in: true,
|
require_logged_in: true,
|
||||||
require_valid_csrf_token: true,
|
require_valid_csrf_token: true,
|
||||||
);
|
);
|
||||||
|
@ -38,9 +35,10 @@ class ListTrackingsAction extends Action
|
||||||
/**
|
/**
|
||||||
* Returns all trackings of the current user.
|
* Returns all trackings of the current user.
|
||||||
*
|
*
|
||||||
|
* @param array<int|string, mixed> $inputs ignored
|
||||||
* @return mixed[] all trackings of the current user
|
* @return mixed[] all trackings of the current user
|
||||||
*/
|
*/
|
||||||
function handle(): array
|
public function handle(array $inputs): array
|
||||||
{
|
{
|
||||||
return $this->tracking_manager->list_trackings($_SESSION["uuid"]);
|
return $this->tracking_manager->list_trackings($_SESSION["uuid"]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
namespace com\fwdekker\deathnotifier\tracking;
|
namespace com\fwdekker\deathnotifier\tracking;
|
||||||
|
|
||||||
use com\fwdekker\deathnotifier\Action;
|
use com\fwdekker\deathnotifier\Action;
|
||||||
use com\fwdekker\deathnotifier\ActionMethod;
|
|
||||||
use com\fwdekker\deathnotifier\validator\IsNotBlankRule;
|
use com\fwdekker\deathnotifier\validator\IsNotBlankRule;
|
||||||
|
|
||||||
|
|
||||||
|
@ -26,8 +25,6 @@ class RemoveTrackingAction extends Action
|
||||||
public function __construct(TrackingManager $tracking_manager)
|
public function __construct(TrackingManager $tracking_manager)
|
||||||
{
|
{
|
||||||
parent::__construct(
|
parent::__construct(
|
||||||
ActionMethod::POST,
|
|
||||||
"remove-tracking",
|
|
||||||
require_logged_in: true,
|
require_logged_in: true,
|
||||||
require_valid_csrf_token: true,
|
require_valid_csrf_token: true,
|
||||||
rule_lists: ["person_name" => [new IsNotBlankRule()]],
|
rule_lists: ["person_name" => [new IsNotBlankRule()]],
|
||||||
|
@ -38,13 +35,14 @@ class RemoveTrackingAction extends Action
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the current user's tracking of `$_POST["person_name"]`.
|
* Removes the current user's tracking of the specified person.
|
||||||
*
|
*
|
||||||
|
* @param array<int|string, mixed> $inputs `"person_name": string`: the name of the person to stop tracking
|
||||||
* @return null
|
* @return null
|
||||||
*/
|
*/
|
||||||
function handle(): mixed
|
public function handle(array $inputs): mixed
|
||||||
{
|
{
|
||||||
$this->tracking_manager->remove_tracking($_SESSION["uuid"], $_POST["person_name"]);
|
$this->tracking_manager->remove_tracking($_SESSION["uuid"], $inputs["person_name"]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ namespace com\fwdekker\deathnotifier\tracking;
|
||||||
|
|
||||||
use com\fwdekker\deathnotifier\Action;
|
use com\fwdekker\deathnotifier\Action;
|
||||||
use com\fwdekker\deathnotifier\ActionException;
|
use com\fwdekker\deathnotifier\ActionException;
|
||||||
use com\fwdekker\deathnotifier\ActionMethod;
|
|
||||||
use com\fwdekker\deathnotifier\Config;
|
use com\fwdekker\deathnotifier\Config;
|
||||||
use com\fwdekker\deathnotifier\Database;
|
use com\fwdekker\deathnotifier\Database;
|
||||||
use com\fwdekker\deathnotifier\LoggerUtil;
|
use com\fwdekker\deathnotifier\LoggerUtil;
|
||||||
|
@ -55,8 +54,6 @@ class UpdateTrackingsAction extends Action
|
||||||
public function __construct(PDO $conn, TrackingManager $tracking_manager, MediaWiki $mediawiki, MailManager $mailer)
|
public function __construct(PDO $conn, TrackingManager $tracking_manager, MediaWiki $mediawiki, MailManager $mailer)
|
||||||
{
|
{
|
||||||
parent::__construct(
|
parent::__construct(
|
||||||
ActionMethod::CLI,
|
|
||||||
"update-trackings",
|
|
||||||
rule_lists: [
|
rule_lists: [
|
||||||
"password" => [new IsEqualToRule(Config::get()["admin"]["cli_secret"], "Incorrect password.")]
|
"password" => [new IsEqualToRule(Config::get()["admin"]["cli_secret"], "Incorrect password.")]
|
||||||
],
|
],
|
||||||
|
@ -73,10 +70,11 @@ class UpdateTrackingsAction extends Action
|
||||||
/**
|
/**
|
||||||
* Updates all trackings that users have added.
|
* Updates all trackings that users have added.
|
||||||
*
|
*
|
||||||
|
* @param array<int|string, mixed> $inputs ignored
|
||||||
* @return null
|
* @return null
|
||||||
* @throws ActionException if the Wikipedia API could not be reached
|
* @throws ActionException if the Wikipedia API could not be reached
|
||||||
*/
|
*/
|
||||||
public function handle(): mixed
|
public function handle(array $inputs): mixed
|
||||||
{
|
{
|
||||||
$names = $this->tracking_manager->list_all_unique_person_names();
|
$names = $this->tracking_manager->list_all_unique_person_names();
|
||||||
if (empty($names)) return null;
|
if (empty($names)) return null;
|
||||||
|
|
|
@ -4,13 +4,12 @@ namespace com\fwdekker\deathnotifier\user;
|
||||||
|
|
||||||
use com\fwdekker\deathnotifier\Action;
|
use com\fwdekker\deathnotifier\Action;
|
||||||
use com\fwdekker\deathnotifier\ActionException;
|
use com\fwdekker\deathnotifier\ActionException;
|
||||||
use com\fwdekker\deathnotifier\ActionMethod;
|
|
||||||
use com\fwdekker\deathnotifier\Config;
|
use com\fwdekker\deathnotifier\Config;
|
||||||
use com\fwdekker\deathnotifier\Database;
|
use com\fwdekker\deathnotifier\Database;
|
||||||
use com\fwdekker\deathnotifier\mailer\Email;
|
use com\fwdekker\deathnotifier\mailer\Email;
|
||||||
use com\fwdekker\deathnotifier\mailer\MailManager;
|
use com\fwdekker\deathnotifier\mailer\MailManager;
|
||||||
use com\fwdekker\deathnotifier\validator\IsEmailRule;
|
use com\fwdekker\deathnotifier\validator\IsEmailRule;
|
||||||
use com\fwdekker\deathnotifier\validator\ValidationException;
|
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||||
use PDO;
|
use PDO;
|
||||||
|
|
||||||
|
|
||||||
|
@ -43,8 +42,6 @@ class ChangeEmailAction extends Action
|
||||||
public function __construct(PDO $conn, UserManager $user_manager, MailManager $mail_manager)
|
public function __construct(PDO $conn, UserManager $user_manager, MailManager $mail_manager)
|
||||||
{
|
{
|
||||||
parent::__construct(
|
parent::__construct(
|
||||||
ActionMethod::POST,
|
|
||||||
"update-email",
|
|
||||||
require_logged_in: true,
|
require_logged_in: true,
|
||||||
require_valid_csrf_token: true,
|
require_valid_csrf_token: true,
|
||||||
rule_lists: ["email" => [new IsEmailRule()]],
|
rule_lists: ["email" => [new IsEmailRule()]],
|
||||||
|
@ -59,23 +56,24 @@ class ChangeEmailAction extends Action
|
||||||
/**
|
/**
|
||||||
* Changes the user's email address.
|
* Changes the user's email address.
|
||||||
*
|
*
|
||||||
|
* @param array<int|string, mixed> $inputs `"email": string`: the email address to change to
|
||||||
* @return null
|
* @return null
|
||||||
* @throws ValidationException if the email address is not different or the email address is already used
|
* @throws InvalidInputException if the email address is not different or the email address is already used
|
||||||
*/
|
*/
|
||||||
function handle(): mixed
|
function handle(array $inputs): mixed
|
||||||
{
|
{
|
||||||
Database::transaction($this->conn, function () {
|
Database::transaction($this->conn, function () use ($inputs) {
|
||||||
$user_data = $this->user_manager->get_user_by_uuid($_SESSION["uuid"]);
|
$user_data = $this->user_manager->get_user_by_uuid($_SESSION["uuid"]);
|
||||||
if ($user_data === null)
|
if ($user_data === null)
|
||||||
throw new ActionException("Failed to retrieve account data. Refresh the page and try again.");
|
throw new ActionException("Failed to retrieve account data. Refresh the page and try again.");
|
||||||
if ($_POST["email"] === $user_data["email"])
|
if ($inputs["email"] === $user_data["email"])
|
||||||
throw new ValidationException("That is already your email address.", "email");
|
throw new InvalidInputException("That is already your email address.", "email");
|
||||||
if ($this->user_manager->has_user_with_email($_POST["email"]))
|
if ($this->user_manager->has_user_with_email($inputs["email"]))
|
||||||
throw new ValidationException("That email address is already in use by someone else.", "email");
|
throw new InvalidInputException("That email address is already in use by someone else.", "email");
|
||||||
|
|
||||||
$token = $this->user_manager->set_email($_SESSION["uuid"], $_POST["email"]);
|
$token = $this->user_manager->set_email($_SESSION["uuid"], $inputs["email"]);
|
||||||
// TODO: Also send email to old email address
|
// TODO: Also send email to old email address
|
||||||
$this->mail_manager->queue_email(new ChangeEmailEmail($_POST["email"], $token));
|
$this->mail_manager->queue_email(new ChangeEmailEmail($inputs["email"], $token));
|
||||||
});
|
});
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -4,14 +4,13 @@ namespace com\fwdekker\deathnotifier\user;
|
||||||
|
|
||||||
use com\fwdekker\deathnotifier\Action;
|
use com\fwdekker\deathnotifier\Action;
|
||||||
use com\fwdekker\deathnotifier\ActionException;
|
use com\fwdekker\deathnotifier\ActionException;
|
||||||
use com\fwdekker\deathnotifier\ActionMethod;
|
|
||||||
use com\fwdekker\deathnotifier\Config;
|
use com\fwdekker\deathnotifier\Config;
|
||||||
use com\fwdekker\deathnotifier\Database;
|
use com\fwdekker\deathnotifier\Database;
|
||||||
use com\fwdekker\deathnotifier\mailer\Email;
|
use com\fwdekker\deathnotifier\mailer\Email;
|
||||||
use com\fwdekker\deathnotifier\mailer\MailManager;
|
use com\fwdekker\deathnotifier\mailer\MailManager;
|
||||||
use com\fwdekker\deathnotifier\validator\HasLengthRule;
|
use com\fwdekker\deathnotifier\validator\HasLengthRule;
|
||||||
use com\fwdekker\deathnotifier\validator\IsSetRule;
|
use com\fwdekker\deathnotifier\validator\IsSetRule;
|
||||||
use com\fwdekker\deathnotifier\validator\ValidationException;
|
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||||
use PDO;
|
use PDO;
|
||||||
|
|
||||||
|
|
||||||
|
@ -44,8 +43,6 @@ class ChangePasswordAction extends Action
|
||||||
public function __construct(PDO $conn, UserManager $user_manager, MailManager $mail_manager)
|
public function __construct(PDO $conn, UserManager $user_manager, MailManager $mail_manager)
|
||||||
{
|
{
|
||||||
parent::__construct(
|
parent::__construct(
|
||||||
ActionMethod::POST,
|
|
||||||
"update-password",
|
|
||||||
require_logged_in: true,
|
require_logged_in: true,
|
||||||
require_valid_csrf_token: true,
|
require_valid_csrf_token: true,
|
||||||
rule_lists: [
|
rule_lists: [
|
||||||
|
@ -65,19 +62,21 @@ class ChangePasswordAction extends Action
|
||||||
/**
|
/**
|
||||||
* Changes the user's password.
|
* Changes the user's password.
|
||||||
*
|
*
|
||||||
|
* @param array<int|string, mixed> $inputs `"password_old": string`: the old password, `"password_new": string`: the
|
||||||
|
* new password
|
||||||
* @return null
|
* @return null
|
||||||
* @throws ValidationException if the old password is incorrect
|
* @throws InvalidInputException if the old password is incorrect
|
||||||
*/
|
*/
|
||||||
function handle(): mixed
|
function handle(array $inputs): mixed
|
||||||
{
|
{
|
||||||
Database::transaction($this->conn, function () {
|
Database::transaction($this->conn, function () use ($inputs) {
|
||||||
$user_data = $this->user_manager->get_user_by_uuid($_SESSION["uuid"]);
|
$user_data = $this->user_manager->get_user_by_uuid($_SESSION["uuid"]);
|
||||||
if ($user_data === null)
|
if ($user_data === null)
|
||||||
throw new ActionException("Failed to retrieve account data. Refresh the page and try again.");
|
throw new ActionException("Failed to retrieve account data. Refresh the page and try again.");
|
||||||
if (!password_verify($user_data["password"], $_POST["password_old"]))
|
if (!password_verify($user_data["password"], $inputs["password_old"]))
|
||||||
throw new ValidationException("Incorrect old password.", "password_old");
|
throw new InvalidInputException("Incorrect old password.", "password_old");
|
||||||
|
|
||||||
$this->user_manager->set_password($_SESSION["uuid"], $_POST["password_new"]);
|
$this->user_manager->set_password($_SESSION["uuid"], $inputs["password_new"]);
|
||||||
$this->mail_manager->queue_email(new ChangePasswordEmail($user_data["email"]));
|
$this->mail_manager->queue_email(new ChangePasswordEmail($user_data["email"]));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ namespace com\fwdekker\deathnotifier\user;
|
||||||
|
|
||||||
use com\fwdekker\deathnotifier\Action;
|
use com\fwdekker\deathnotifier\Action;
|
||||||
use com\fwdekker\deathnotifier\ActionException;
|
use com\fwdekker\deathnotifier\ActionException;
|
||||||
use com\fwdekker\deathnotifier\ActionMethod;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -25,12 +24,7 @@ class GetPublicUserDataAction extends Action
|
||||||
*/
|
*/
|
||||||
public function __construct(UserManager $user_manager)
|
public function __construct(UserManager $user_manager)
|
||||||
{
|
{
|
||||||
parent::__construct(
|
parent::__construct(require_logged_in: true, require_valid_csrf_token: true);
|
||||||
ActionMethod::GET,
|
|
||||||
"get-user-data",
|
|
||||||
require_logged_in: true,
|
|
||||||
require_valid_csrf_token: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->user_manager = $user_manager;
|
$this->user_manager = $user_manager;
|
||||||
}
|
}
|
||||||
|
@ -39,11 +33,12 @@ class GetPublicUserDataAction extends Action
|
||||||
/**
|
/**
|
||||||
* Returns the user's public data.
|
* Returns the user's public data.
|
||||||
*
|
*
|
||||||
|
* @param array<int|string, mixed> $inputs ignored
|
||||||
* @return array{"email": string, "email_verified": bool, "email_notifications_enabled": bool,
|
* @return array{"email": string, "email_verified": bool, "email_notifications_enabled": bool,
|
||||||
* "password_last_change": int} the user's public data
|
* "password_last_change": int} the user's public data
|
||||||
* @throws ActionException if the user's data could not be retrieved
|
* @throws ActionException if the user's data could not be retrieved
|
||||||
*/
|
*/
|
||||||
function handle(): array
|
function handle(array $inputs): array
|
||||||
{
|
{
|
||||||
$user_data = $this->user_manager->get_user_by_uuid($_SESSION["uuid"]);
|
$user_data = $this->user_manager->get_user_by_uuid($_SESSION["uuid"]);
|
||||||
if ($user_data === null)
|
if ($user_data === null)
|
||||||
|
|
|
@ -4,8 +4,8 @@ namespace com\fwdekker\deathnotifier\user;
|
||||||
|
|
||||||
use com\fwdekker\deathnotifier\Action;
|
use com\fwdekker\deathnotifier\Action;
|
||||||
use com\fwdekker\deathnotifier\ActionException;
|
use com\fwdekker\deathnotifier\ActionException;
|
||||||
use com\fwdekker\deathnotifier\ActionMethod;
|
|
||||||
use com\fwdekker\deathnotifier\validator\IsEmailRule;
|
use com\fwdekker\deathnotifier\validator\IsEmailRule;
|
||||||
|
use com\fwdekker\deathnotifier\validator\IsSetRule;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -27,11 +27,12 @@ class LoginAction extends Action
|
||||||
public function __construct(UserManager $user_manager)
|
public function __construct(UserManager $user_manager)
|
||||||
{
|
{
|
||||||
parent::__construct(
|
parent::__construct(
|
||||||
ActionMethod::POST,
|
|
||||||
"login",
|
|
||||||
require_logged_out: true,
|
require_logged_out: true,
|
||||||
require_valid_csrf_token: true,
|
require_valid_csrf_token: true,
|
||||||
rule_lists: ["email" => [new IsEmailRule()]],
|
rule_lists: [
|
||||||
|
"email" => [new IsEmailRule()],
|
||||||
|
"password" => [new IsSetRule()],
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->user_manager = $user_manager;
|
$this->user_manager = $user_manager;
|
||||||
|
@ -41,15 +42,17 @@ class LoginAction extends Action
|
||||||
/**
|
/**
|
||||||
* Logs in the user.
|
* Logs in the user.
|
||||||
*
|
*
|
||||||
|
* @param array<int|string, mixed> $inputs `"email": string`: the email to log in with, `"password": string`: the
|
||||||
|
* password to log in with
|
||||||
* @return null
|
* @return null
|
||||||
* @throws ActionException if the user's data could not be retrieved or if the given credentials are incorrect
|
* @throws ActionException if the user's data could not be retrieved or if the given credentials are incorrect
|
||||||
*/
|
*/
|
||||||
function handle(): mixed
|
function handle(array $inputs): mixed
|
||||||
{
|
{
|
||||||
$user_data = $this->user_manager->get_user_by_email($_POST["email"]);
|
$user_data = $this->user_manager->get_user_by_email($inputs["email"]);
|
||||||
if ($user_data === null)
|
if ($user_data === null)
|
||||||
throw new ActionException("Failed to retrieve account data. Refresh the page and try again.");
|
throw new ActionException("Incorrect combination of email and password.", "password");
|
||||||
if (!password_verify($_POST["password"], $user_data["password"]))
|
if (!password_verify($inputs["password"], $user_data["password"]))
|
||||||
throw new ActionException("Incorrect combination of email and password.", "password");
|
throw new ActionException("Incorrect combination of email and password.", "password");
|
||||||
|
|
||||||
$_SESSION["uuid"] = $user_data["uuid"];
|
$_SESSION["uuid"] = $user_data["uuid"];
|
||||||
|
|
|
@ -4,7 +4,6 @@ namespace com\fwdekker\deathnotifier\user;
|
||||||
|
|
||||||
use com\fwdekker\deathnotifier\Action;
|
use com\fwdekker\deathnotifier\Action;
|
||||||
use com\fwdekker\deathnotifier\ActionException;
|
use com\fwdekker\deathnotifier\ActionException;
|
||||||
use com\fwdekker\deathnotifier\ActionMethod;
|
|
||||||
use com\fwdekker\deathnotifier\Util;
|
use com\fwdekker\deathnotifier\Util;
|
||||||
use Exception;
|
use Exception;
|
||||||
|
|
||||||
|
@ -19,22 +18,18 @@ class LogoutAction extends Action
|
||||||
*/
|
*/
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct(
|
parent::__construct(require_logged_in: true, require_valid_csrf_token: true);
|
||||||
ActionMethod::POST,
|
|
||||||
"logout",
|
|
||||||
require_logged_in: true,
|
|
||||||
require_valid_csrf_token: true,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Terminates the current user session.
|
* Terminates the current user session.
|
||||||
*
|
*
|
||||||
|
* @param array<int|string, mixed> $inputs ignored
|
||||||
* @return null
|
* @return null
|
||||||
* @throws ActionException if no CSRF token could be generated
|
* @throws ActionException if no CSRF token could be generated
|
||||||
*/
|
*/
|
||||||
function handle(): mixed
|
function handle(array $inputs): mixed
|
||||||
{
|
{
|
||||||
session_destroy();
|
session_destroy();
|
||||||
session_start();
|
session_start();
|
||||||
|
|
|
@ -4,14 +4,13 @@ namespace com\fwdekker\deathnotifier\user;
|
||||||
|
|
||||||
use com\fwdekker\deathnotifier\Action;
|
use com\fwdekker\deathnotifier\Action;
|
||||||
use com\fwdekker\deathnotifier\ActionException;
|
use com\fwdekker\deathnotifier\ActionException;
|
||||||
use com\fwdekker\deathnotifier\ActionMethod;
|
|
||||||
use com\fwdekker\deathnotifier\Config;
|
use com\fwdekker\deathnotifier\Config;
|
||||||
use com\fwdekker\deathnotifier\Database;
|
use com\fwdekker\deathnotifier\Database;
|
||||||
use com\fwdekker\deathnotifier\mailer\Email;
|
use com\fwdekker\deathnotifier\mailer\Email;
|
||||||
use com\fwdekker\deathnotifier\mailer\MailManager;
|
use com\fwdekker\deathnotifier\mailer\MailManager;
|
||||||
use com\fwdekker\deathnotifier\validator\HasLengthRule;
|
use com\fwdekker\deathnotifier\validator\HasLengthRule;
|
||||||
use com\fwdekker\deathnotifier\validator\IsEmailRule;
|
use com\fwdekker\deathnotifier\validator\IsEmailRule;
|
||||||
use com\fwdekker\deathnotifier\validator\ValidationException;
|
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||||
use PDO;
|
use PDO;
|
||||||
|
|
||||||
|
|
||||||
|
@ -44,8 +43,6 @@ class RegisterAction extends Action
|
||||||
public function __construct(PDO $conn, UserManager $user_manager, MailManager $mail_manager)
|
public function __construct(PDO $conn, UserManager $user_manager, MailManager $mail_manager)
|
||||||
{
|
{
|
||||||
parent::__construct(
|
parent::__construct(
|
||||||
ActionMethod::POST,
|
|
||||||
"register",
|
|
||||||
require_logged_out: true,
|
require_logged_out: true,
|
||||||
require_valid_csrf_token: true,
|
require_valid_csrf_token: true,
|
||||||
rule_lists: [
|
rule_lists: [
|
||||||
|
@ -63,22 +60,24 @@ class RegisterAction extends Action
|
||||||
/**
|
/**
|
||||||
* Registers a new user.
|
* Registers a new user.
|
||||||
*
|
*
|
||||||
|
* @param array<int|string, mixed> $inputs `"email": string`: the email address to register, `"password": string`:
|
||||||
|
* the password to use for the new account
|
||||||
* @return null
|
* @return null
|
||||||
* @throws ValidationException if the email address is already used
|
* @throws InvalidInputException if the email address is already used
|
||||||
*/
|
*/
|
||||||
function handle(): mixed
|
function handle(array $inputs): mixed
|
||||||
{
|
{
|
||||||
Database::transaction($this->conn, function () {
|
Database::transaction($this->conn, function () use ($inputs) {
|
||||||
if ($this->user_manager->has_user_with_email($_POST["email"]))
|
if ($this->user_manager->has_user_with_email($inputs["email"]))
|
||||||
throw new ValidationException("That email address already in use by someone else.", "email");
|
throw new InvalidInputException("That email address already in use by someone else.", "email");
|
||||||
|
|
||||||
$uuid = $this->user_manager->add_user($_POST["email"], $_POST["password"]);
|
$uuid = $this->user_manager->add_user($inputs["email"], $inputs["password"]);
|
||||||
$user_data = $this->user_manager->get_user_by_uuid($uuid);
|
$user_data = $this->user_manager->get_user_by_uuid($uuid);
|
||||||
if ($user_data === null)
|
if ($user_data === null)
|
||||||
throw new ActionException("Failed to retrieve account data. Refresh the page and try again.");
|
throw new ActionException("Failed to retrieve account data. Refresh the page and try again.");
|
||||||
$token = $user_data["email_verification_token"];
|
$token = $user_data["email_verification_token"];
|
||||||
|
|
||||||
$this->mail_manager->queue_email(new RegisterEmail($_POST["email"], $token));
|
$this->mail_manager->queue_email(new RegisterEmail($inputs["email"], $token));
|
||||||
});
|
});
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -3,13 +3,12 @@
|
||||||
namespace com\fwdekker\deathnotifier\user;
|
namespace com\fwdekker\deathnotifier\user;
|
||||||
|
|
||||||
use com\fwdekker\deathnotifier\Action;
|
use com\fwdekker\deathnotifier\Action;
|
||||||
use com\fwdekker\deathnotifier\ActionMethod;
|
|
||||||
use com\fwdekker\deathnotifier\Config;
|
use com\fwdekker\deathnotifier\Config;
|
||||||
use com\fwdekker\deathnotifier\Database;
|
use com\fwdekker\deathnotifier\Database;
|
||||||
use com\fwdekker\deathnotifier\mailer\Email;
|
use com\fwdekker\deathnotifier\mailer\Email;
|
||||||
use com\fwdekker\deathnotifier\mailer\MailManager;
|
use com\fwdekker\deathnotifier\mailer\MailManager;
|
||||||
use com\fwdekker\deathnotifier\Util;
|
use com\fwdekker\deathnotifier\Util;
|
||||||
use com\fwdekker\deathnotifier\validator\ValidationException;
|
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||||
use PDO;
|
use PDO;
|
||||||
|
|
||||||
|
|
||||||
|
@ -41,12 +40,7 @@ class ResendVerifyEmailAction extends Action
|
||||||
*/
|
*/
|
||||||
public function __construct(PDO $conn, UserManager $user_manager, MailManager $mail_manager)
|
public function __construct(PDO $conn, UserManager $user_manager, MailManager $mail_manager)
|
||||||
{
|
{
|
||||||
parent::__construct(
|
parent::__construct(require_logged_in: true, require_valid_csrf_token: true);
|
||||||
ActionMethod::POST,
|
|
||||||
"resend-verify-email",
|
|
||||||
require_logged_in: true,
|
|
||||||
require_valid_csrf_token: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->conn = $conn;
|
$this->conn = $conn;
|
||||||
$this->user_manager = $user_manager;
|
$this->user_manager = $user_manager;
|
||||||
|
@ -57,23 +51,24 @@ class ResendVerifyEmailAction extends Action
|
||||||
/**
|
/**
|
||||||
* Resets the email verification process and sends a new verification email.
|
* Resets the email verification process and sends a new verification email.
|
||||||
*
|
*
|
||||||
|
* @param array<int|string, mixed> $inputs ignored
|
||||||
* @return null
|
* @return null
|
||||||
* @throws ValidationException if the email address is already verified or if a verification email was sent too
|
* @throws InvalidInputException if the email address is already verified or if a verification email was sent too
|
||||||
* recently
|
* recently
|
||||||
*/
|
*/
|
||||||
function handle(): mixed
|
function handle(array $inputs): mixed
|
||||||
{
|
{
|
||||||
Database::transaction($this->conn, function () {
|
Database::transaction($this->conn, function () use ($inputs) {
|
||||||
$user_data = $this->user_manager->get_user_by_uuid($_SESSION["uuid"]);
|
$user_data = $this->user_manager->get_user_by_uuid($_SESSION["uuid"]);
|
||||||
if ($user_data["email_verification_token"] === null)
|
if ($user_data["email_verification_token"] === null)
|
||||||
throw new ValidationException("Your email address is already verified.");
|
throw new InvalidInputException("Your email address is already verified.");
|
||||||
|
|
||||||
$minutes_left = Util::minutes_until_interval_elapsed(
|
$minutes_left = Util::minutes_until_interval_elapsed(
|
||||||
$user_data["email_verification_token_timestamp"],
|
$user_data["email_verification_token_timestamp"],
|
||||||
UserManager::MINUTES_BETWEEN_VERIFICATION_EMAILS
|
UserManager::MINUTES_BETWEEN_VERIFICATION_EMAILS
|
||||||
);
|
);
|
||||||
if ($minutes_left > 0) {
|
if ($minutes_left > 0) {
|
||||||
throw new ValidationException(
|
throw new InvalidInputException(
|
||||||
"A verification email was sent recently. " .
|
"A verification email was sent recently. " .
|
||||||
"Please wait $minutes_left more minute(s) before requesting a new email."
|
"Please wait $minutes_left more minute(s) before requesting a new email."
|
||||||
);
|
);
|
||||||
|
|
|
@ -3,14 +3,14 @@
|
||||||
namespace com\fwdekker\deathnotifier\user;
|
namespace com\fwdekker\deathnotifier\user;
|
||||||
|
|
||||||
use com\fwdekker\deathnotifier\Action;
|
use com\fwdekker\deathnotifier\Action;
|
||||||
use com\fwdekker\deathnotifier\ActionMethod;
|
|
||||||
use com\fwdekker\deathnotifier\Config;
|
use com\fwdekker\deathnotifier\Config;
|
||||||
use com\fwdekker\deathnotifier\Database;
|
use com\fwdekker\deathnotifier\Database;
|
||||||
use com\fwdekker\deathnotifier\mailer\Email;
|
use com\fwdekker\deathnotifier\mailer\Email;
|
||||||
use com\fwdekker\deathnotifier\mailer\MailManager;
|
use com\fwdekker\deathnotifier\mailer\MailManager;
|
||||||
use com\fwdekker\deathnotifier\validator\HasLengthRule;
|
use com\fwdekker\deathnotifier\validator\HasLengthRule;
|
||||||
use com\fwdekker\deathnotifier\validator\IsEmailRule;
|
use com\fwdekker\deathnotifier\validator\IsEmailRule;
|
||||||
use com\fwdekker\deathnotifier\validator\ValidationException;
|
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||||
|
use com\fwdekker\deathnotifier\validator\IsSetRule;
|
||||||
use PDO;
|
use PDO;
|
||||||
|
|
||||||
|
|
||||||
|
@ -43,12 +43,11 @@ class ResetPasswordAction extends Action
|
||||||
public function __construct(PDO $conn, UserManager $user_manager, MailManager $mail_manager)
|
public function __construct(PDO $conn, UserManager $user_manager, MailManager $mail_manager)
|
||||||
{
|
{
|
||||||
parent::__construct(
|
parent::__construct(
|
||||||
ActionMethod::POST,
|
|
||||||
"reset-password",
|
|
||||||
require_valid_csrf_token: true,
|
require_valid_csrf_token: true,
|
||||||
rule_lists: [
|
rule_lists: [
|
||||||
"email" => [new IsEmailRule()],
|
"email" => [new IsEmailRule()],
|
||||||
"password" => [new HasLengthRule(UserManager::MIN_PASSWORD_LENGTH, UserManager::MAX_PASSWORD_LENGTH)],
|
"password" => [new HasLengthRule(UserManager::MIN_PASSWORD_LENGTH, UserManager::MAX_PASSWORD_LENGTH)],
|
||||||
|
"reset_token" => [new IsSetRule()],
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -61,19 +60,21 @@ class ResetPasswordAction extends Action
|
||||||
/**
|
/**
|
||||||
* Resets the user's password after they forgot it.
|
* Resets the user's password after they forgot it.
|
||||||
*
|
*
|
||||||
|
* @param array<int|string, mixed> $inputs `"email": string`: the email address of the account to reset the password
|
||||||
|
* of, `"password": string`: the new password to set, `"reset_token": string`: the token to reset the password with
|
||||||
* @return null
|
* @return null
|
||||||
*/
|
*/
|
||||||
function handle(): mixed
|
function handle(array $inputs): mixed
|
||||||
{
|
{
|
||||||
Database::transaction($this->conn, function () {
|
Database::transaction($this->conn, function () use ($inputs) {
|
||||||
$user_data = $this->user_manager->get_user_by_email($_POST["email"]);
|
$user_data = $this->user_manager->get_user_by_email($inputs["email"]);
|
||||||
if ($_GET["reset_token"] !== $user_data["password_reset_token"])
|
if ($inputs["reset_token"] !== $user_data["password_reset_token"])
|
||||||
throw new ValidationException(
|
throw new InvalidInputException(
|
||||||
"This password reset link is invalid. Maybe you already reset your password?"
|
"This password reset link is invalid. Maybe you already reset your password?"
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->user_manager->set_password($user_data["uuid"], $_POST["password"]);
|
$this->user_manager->set_password($user_data["uuid"], $inputs["password"]);
|
||||||
$this->mail_manager->queue_email(new ResetPasswordEmail($_POST["email"]));
|
$this->mail_manager->queue_email(new ResetPasswordEmail($inputs["email"]));
|
||||||
});
|
});
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -3,14 +3,13 @@
|
||||||
namespace com\fwdekker\deathnotifier\user;
|
namespace com\fwdekker\deathnotifier\user;
|
||||||
|
|
||||||
use com\fwdekker\deathnotifier\Action;
|
use com\fwdekker\deathnotifier\Action;
|
||||||
use com\fwdekker\deathnotifier\ActionMethod;
|
|
||||||
use com\fwdekker\deathnotifier\Config;
|
use com\fwdekker\deathnotifier\Config;
|
||||||
use com\fwdekker\deathnotifier\Database;
|
use com\fwdekker\deathnotifier\Database;
|
||||||
use com\fwdekker\deathnotifier\mailer\Email;
|
use com\fwdekker\deathnotifier\mailer\Email;
|
||||||
use com\fwdekker\deathnotifier\mailer\MailManager;
|
use com\fwdekker\deathnotifier\mailer\MailManager;
|
||||||
use com\fwdekker\deathnotifier\Util;
|
use com\fwdekker\deathnotifier\Util;
|
||||||
use com\fwdekker\deathnotifier\validator\IsEmailRule;
|
use com\fwdekker\deathnotifier\validator\IsEmailRule;
|
||||||
use com\fwdekker\deathnotifier\validator\ValidationException;
|
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||||
use PDO;
|
use PDO;
|
||||||
|
|
||||||
|
|
||||||
|
@ -43,8 +42,6 @@ class SendPasswordResetAction extends Action
|
||||||
public function __construct(PDO $conn, UserManager $user_manager, MailManager $mail_manager)
|
public function __construct(PDO $conn, UserManager $user_manager, MailManager $mail_manager)
|
||||||
{
|
{
|
||||||
parent::__construct(
|
parent::__construct(
|
||||||
ActionMethod::POST,
|
|
||||||
"send-password-reset",
|
|
||||||
require_valid_csrf_token: true,
|
require_valid_csrf_token: true,
|
||||||
rule_lists: ["email" => [new IsEmailRule()]],
|
rule_lists: ["email" => [new IsEmailRule()]],
|
||||||
);
|
);
|
||||||
|
@ -58,27 +55,29 @@ class SendPasswordResetAction extends Action
|
||||||
/**
|
/**
|
||||||
* Sends a password reset email.
|
* Sends a password reset email.
|
||||||
*
|
*
|
||||||
|
* @param array<int|string, mixed> $inputs `"email": string`: the email address of the account to send a password
|
||||||
|
* reset email for
|
||||||
* @return null
|
* @return null
|
||||||
* @throws ValidationException if another password reset email was sent too recently
|
* @throws InvalidInputException if another password reset email was sent too recently
|
||||||
*/
|
*/
|
||||||
function handle(): mixed
|
function handle(array $inputs): mixed
|
||||||
{
|
{
|
||||||
Database::transaction($this->conn, function () {
|
Database::transaction($this->conn, function () use ($inputs) {
|
||||||
$user_data = $this->user_manager->get_user_by_email($_POST["email"]);
|
$user_data = $this->user_manager->get_user_by_email($inputs["email"]);
|
||||||
|
|
||||||
$minutes_left = Util::minutes_until_interval_elapsed(
|
$minutes_left = Util::minutes_until_interval_elapsed(
|
||||||
$user_data["password_reset_token_timestamp"],
|
$user_data["password_reset_token_timestamp"],
|
||||||
UserManager::MINUTES_BETWEEN_PASSWORD_RESETS
|
UserManager::MINUTES_BETWEEN_PASSWORD_RESETS
|
||||||
);
|
);
|
||||||
if ($minutes_left > 0) {
|
if ($minutes_left > 0) {
|
||||||
throw new ValidationException(
|
throw new InvalidInputException(
|
||||||
"A password reset email was sent recently. " .
|
"A password reset email was sent recently. " .
|
||||||
"Please wait $minutes_left more minute(s) before requesting a new email."
|
"Please wait $minutes_left more minute(s) before requesting a new email."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$token = $this->user_manager->register_password_reset($_POST["email"]);
|
$token = $this->user_manager->register_password_reset($inputs["email"]);
|
||||||
$this->mail_manager->queue_email(new SendPasswordResetEmail($_POST["email"], $token));
|
$this->mail_manager->queue_email(new SendPasswordResetEmail($inputs["email"], $token));
|
||||||
});
|
});
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -3,10 +3,9 @@
|
||||||
namespace com\fwdekker\deathnotifier\user;
|
namespace com\fwdekker\deathnotifier\user;
|
||||||
|
|
||||||
use com\fwdekker\deathnotifier\Action;
|
use com\fwdekker\deathnotifier\Action;
|
||||||
use com\fwdekker\deathnotifier\ActionMethod;
|
|
||||||
use com\fwdekker\deathnotifier\Database;
|
use com\fwdekker\deathnotifier\Database;
|
||||||
use com\fwdekker\deathnotifier\validator\IsSetRule;
|
use com\fwdekker\deathnotifier\validator\IsSetRule;
|
||||||
use com\fwdekker\deathnotifier\validator\ValidationException;
|
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||||
use PDO;
|
use PDO;
|
||||||
|
|
||||||
|
|
||||||
|
@ -34,13 +33,12 @@ class ToggleNotificationsAction extends Action
|
||||||
public function __construct(PDO $conn, UserManager $user_manager)
|
public function __construct(PDO $conn, UserManager $user_manager)
|
||||||
{
|
{
|
||||||
parent::__construct(
|
parent::__construct(
|
||||||
ActionMethod::POST,
|
|
||||||
"toggle-notifications",
|
|
||||||
require_logged_in: true,
|
require_logged_in: true,
|
||||||
require_valid_csrf_token: true,
|
require_valid_csrf_token: true,
|
||||||
rule_lists: ["enable_notifications" => [new IsSetRule()]]
|
rule_lists: ["enable_notifications" => [new IsSetRule()]]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$this->conn = $conn;
|
||||||
$this->user_manager = $user_manager;
|
$this->user_manager = $user_manager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,17 +46,19 @@ class ToggleNotificationsAction extends Action
|
||||||
/**
|
/**
|
||||||
* Sets whether email notifications are sent.
|
* Sets whether email notifications are sent.
|
||||||
*
|
*
|
||||||
|
* @param array<int|string, mixed> $inputs `"enable_notifications": bool`: `true` if and only if notifications
|
||||||
|
* should be enabled
|
||||||
* @return null
|
* @return null
|
||||||
* @throws ValidationException if the user's email address has not been verified
|
* @throws InvalidInputException if the user's email address has not been verified
|
||||||
*/
|
*/
|
||||||
function handle(): mixed
|
function handle(array $inputs): mixed
|
||||||
{
|
{
|
||||||
Database::transaction($this->conn, function() {
|
Database::transaction($this->conn, function() use ($inputs) {
|
||||||
$user_data = $this->user_manager->get_user_by_uuid($_SESSION["uuid"]);
|
$user_data = $this->user_manager->get_user_by_uuid($_SESSION["uuid"]);
|
||||||
if ($user_data["email_verification_token"] === null)
|
if ($user_data["email_verification_token"] === null)
|
||||||
throw new ValidationException("Please verify your email address before enabling notifications.");
|
throw new InvalidInputException("Please verify your email address before enabling notifications.");
|
||||||
|
|
||||||
$this->user_manager->set_notifications_enabled($_SESSION["uuid"], $_POST["enable_notifications"] == true);
|
$this->user_manager->set_notifications_enabled($_SESSION["uuid"], $inputs["enable_notifications"] == true);
|
||||||
});
|
});
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -4,7 +4,6 @@ namespace com\fwdekker\deathnotifier\user;
|
||||||
|
|
||||||
use com\fwdekker\deathnotifier\Action;
|
use com\fwdekker\deathnotifier\Action;
|
||||||
use com\fwdekker\deathnotifier\ActionException;
|
use com\fwdekker\deathnotifier\ActionException;
|
||||||
use com\fwdekker\deathnotifier\ActionMethod;
|
|
||||||
use com\fwdekker\deathnotifier\Util;
|
use com\fwdekker\deathnotifier\Util;
|
||||||
use com\fwdekker\deathnotifier\validator\HasLengthRule;
|
use com\fwdekker\deathnotifier\validator\HasLengthRule;
|
||||||
use com\fwdekker\deathnotifier\validator\IsSetRule;
|
use com\fwdekker\deathnotifier\validator\IsSetRule;
|
||||||
|
@ -30,8 +29,6 @@ class UserDeleteAction extends Action
|
||||||
public function __construct(UserManager $user_manager)
|
public function __construct(UserManager $user_manager)
|
||||||
{
|
{
|
||||||
parent::__construct(
|
parent::__construct(
|
||||||
ActionMethod::POST,
|
|
||||||
"user-delete",
|
|
||||||
require_logged_in: true,
|
require_logged_in: true,
|
||||||
require_valid_csrf_token: true,
|
require_valid_csrf_token: true,
|
||||||
rule_lists: [
|
rule_lists: [
|
||||||
|
@ -49,10 +46,11 @@ class UserDeleteAction extends Action
|
||||||
/**
|
/**
|
||||||
* Deletes the currently logged-in user, and terminates the current user session.
|
* Deletes the currently logged-in user, and terminates the current user session.
|
||||||
*
|
*
|
||||||
|
* @param array<int|string, mixed> $inputs ignored
|
||||||
* @return null
|
* @return null
|
||||||
* @throws ActionException if no CSRF token could be generated
|
* @throws ActionException if no CSRF token could be generated
|
||||||
*/
|
*/
|
||||||
function handle(): mixed
|
function handle(array $inputs): mixed
|
||||||
{
|
{
|
||||||
$this->user_manager->remove_user_by_uuid($_SESSION["uuid"]);
|
$this->user_manager->remove_user_by_uuid($_SESSION["uuid"]);
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,7 @@
|
||||||
|
|
||||||
namespace com\fwdekker\deathnotifier\user;
|
namespace com\fwdekker\deathnotifier\user;
|
||||||
|
|
||||||
use com\fwdekker\deathnotifier\Database;
|
|
||||||
use com\fwdekker\deathnotifier\LoggerUtil;
|
use com\fwdekker\deathnotifier\LoggerUtil;
|
||||||
use com\fwdekker\deathnotifier\mailer\MailManager;
|
|
||||||
use com\fwdekker\deathnotifier\Response;
|
|
||||||
use Monolog\Logger;
|
use Monolog\Logger;
|
||||||
use PDO;
|
use PDO;
|
||||||
|
|
||||||
|
@ -49,23 +46,17 @@ class UserManager
|
||||||
* @var PDO the database connection to interact with
|
* @var PDO the database connection to interact with
|
||||||
*/
|
*/
|
||||||
private PDO $conn;
|
private PDO $conn;
|
||||||
/**
|
|
||||||
* @var MailManager the mailer to send emails with
|
|
||||||
*/
|
|
||||||
private MailManager $mailer;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new user manager.
|
* Constructs a new user manager.
|
||||||
*
|
*
|
||||||
* @param PDO $conn the database connection to interact with
|
* @param PDO $conn the database connection to interact with
|
||||||
* @param MailManager $mailer the mailer to send emails with
|
|
||||||
*/
|
*/
|
||||||
public function __construct(PDO $conn, MailManager $mailer)
|
public function __construct(PDO $conn)
|
||||||
{
|
{
|
||||||
$this->logger = LoggerUtil::with_name($this::class);
|
$this->logger = LoggerUtil::with_name($this::class);
|
||||||
$this->conn = $conn;
|
$this->conn = $conn;
|
||||||
$this->mailer = $mailer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,10 +3,9 @@
|
||||||
namespace com\fwdekker\deathnotifier\user;
|
namespace com\fwdekker\deathnotifier\user;
|
||||||
|
|
||||||
use com\fwdekker\deathnotifier\Action;
|
use com\fwdekker\deathnotifier\Action;
|
||||||
use com\fwdekker\deathnotifier\ActionMethod;
|
|
||||||
use com\fwdekker\deathnotifier\validator\IsEmailRule;
|
use com\fwdekker\deathnotifier\validator\IsEmailRule;
|
||||||
use com\fwdekker\deathnotifier\validator\IsSetRule;
|
use com\fwdekker\deathnotifier\validator\IsSetRule;
|
||||||
use com\fwdekker\deathnotifier\validator\ValidationException;
|
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -28,12 +27,10 @@ class ValidatePasswordResetTokenAction extends Action
|
||||||
public function __construct(UserManager $user_manager)
|
public function __construct(UserManager $user_manager)
|
||||||
{
|
{
|
||||||
parent::__construct(
|
parent::__construct(
|
||||||
ActionMethod::GET,
|
|
||||||
"validate-password-reset-token",
|
|
||||||
require_valid_csrf_token: true,
|
require_valid_csrf_token: true,
|
||||||
rule_lists: [
|
rule_lists: [
|
||||||
"reset_token" => [new IsSetRule()],
|
|
||||||
"email" => [new IsEmailRule()],
|
"email" => [new IsEmailRule()],
|
||||||
|
"reset_token" => [new IsSetRule()],
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -44,14 +41,16 @@ class ValidatePasswordResetTokenAction extends Action
|
||||||
/**
|
/**
|
||||||
* Checks whether the given password reset token is valid.
|
* Checks whether the given password reset token is valid.
|
||||||
*
|
*
|
||||||
|
* @param array<int|string, mixed> $inputs `"email"`: the email address to validate the reset token of,
|
||||||
|
* `"reset_token": string`: the token to validate
|
||||||
* @return null
|
* @return null
|
||||||
* @throws ValidationException if the password reset link is valid
|
* @throws InvalidInputException if the password reset link is valid
|
||||||
*/
|
*/
|
||||||
function handle(): mixed
|
function handle(array $inputs): mixed
|
||||||
{
|
{
|
||||||
$user_data = $this->user_manager->get_user_by_email($_GET["email"]);
|
$user_data = $this->user_manager->get_user_by_email($inputs["email"]);
|
||||||
if ($_GET["reset_token"] !== $user_data["password_reset_token"])
|
if ($inputs["reset_token"] !== $user_data["password_reset_token"])
|
||||||
throw new ValidationException(
|
throw new InvalidInputException(
|
||||||
"This password reset link is invalid. Maybe you already reset your password?"
|
"This password reset link is invalid. Maybe you already reset your password?"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -3,13 +3,12 @@
|
||||||
namespace com\fwdekker\deathnotifier\user;
|
namespace com\fwdekker\deathnotifier\user;
|
||||||
|
|
||||||
use com\fwdekker\deathnotifier\Action;
|
use com\fwdekker\deathnotifier\Action;
|
||||||
use com\fwdekker\deathnotifier\ActionMethod;
|
|
||||||
use com\fwdekker\deathnotifier\Database;
|
use com\fwdekker\deathnotifier\Database;
|
||||||
use com\fwdekker\deathnotifier\mailer\MailManager;
|
use com\fwdekker\deathnotifier\mailer\MailManager;
|
||||||
use com\fwdekker\deathnotifier\Util;
|
use com\fwdekker\deathnotifier\Util;
|
||||||
use com\fwdekker\deathnotifier\validator\IsEmailRule;
|
use com\fwdekker\deathnotifier\validator\IsEmailRule;
|
||||||
use com\fwdekker\deathnotifier\validator\IsSetRule;
|
use com\fwdekker\deathnotifier\validator\IsSetRule;
|
||||||
use com\fwdekker\deathnotifier\validator\ValidationException;
|
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||||
use PDO;
|
use PDO;
|
||||||
|
|
||||||
|
|
||||||
|
@ -42,8 +41,6 @@ class VerifyEmailAction extends Action
|
||||||
public function __construct(PDO $conn, UserManager $user_manager, MailManager $mail_manager)
|
public function __construct(PDO $conn, UserManager $user_manager, MailManager $mail_manager)
|
||||||
{
|
{
|
||||||
parent::__construct(
|
parent::__construct(
|
||||||
ActionMethod::POST,
|
|
||||||
"verify-email",
|
|
||||||
require_valid_csrf_token: true,
|
require_valid_csrf_token: true,
|
||||||
rule_lists: [
|
rule_lists: [
|
||||||
"email" => [new IsEmailRule()],
|
"email" => [new IsEmailRule()],
|
||||||
|
@ -60,17 +57,19 @@ class VerifyEmailAction extends Action
|
||||||
/**
|
/**
|
||||||
* Verifies the user's email address.
|
* Verifies the user's email address.
|
||||||
*
|
*
|
||||||
|
* @param array<int|string, mixed> $inputs `"email"`: the email address to verify, `"verify_token": string`: the
|
||||||
|
* token to verify the email address with
|
||||||
* @return null
|
* @return null
|
||||||
* @throws ValidationException if the email address does not exist, if the email is already verified, or if the
|
* @throws InvalidInputException if the email address does not exist, if the email is already verified, or if the
|
||||||
* token expired
|
* token expired
|
||||||
*/
|
*/
|
||||||
function handle(): mixed
|
function handle(array $inputs): mixed
|
||||||
{
|
{
|
||||||
Database::transaction($this->conn, function () {
|
Database::transaction($this->conn, function () use ($inputs) {
|
||||||
// TODO: Validate that the email even exists
|
// TODO: Validate that the email even exists
|
||||||
$user_data = $this->user_manager->get_user_by_email($_POST["email"]);
|
$user_data = $this->user_manager->get_user_by_email($inputs["email"]);
|
||||||
if ($user_data["email_verification_token"] !== $_POST["verify_token"])
|
if ($user_data["email_verification_token"] !== $inputs["verify_token"])
|
||||||
throw new ValidationException(
|
throw new InvalidInputException(
|
||||||
"Failed to verify email address. Maybe you already verified your email address?"
|
"Failed to verify email address. Maybe you already verified your email address?"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -79,7 +78,7 @@ class VerifyEmailAction extends Action
|
||||||
UserManager::MINUTES_VALID_VERIFICATION
|
UserManager::MINUTES_VALID_VERIFICATION
|
||||||
);
|
);
|
||||||
if ($minutes_remaining < 0)
|
if ($minutes_remaining < 0)
|
||||||
throw new ValidationException(
|
throw new InvalidInputException(
|
||||||
"This email verification link has expired. Log in and request a new verification email."
|
"This email verification link has expired. Log in and request a new verification email."
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -41,20 +41,20 @@ class HasLengthRule extends Rule
|
||||||
* @param array<int|string, mixed> $inputs the list of inputs in which the value at `key` should be checked
|
* @param array<int|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
|
* @param string $key the key in `inputs` of the input to check
|
||||||
* @return void if `$inputs[$key]` is of the specified length
|
* @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
|
* @throws InvalidInputException if `$inputs[$key]` is not set or is not of the specified length
|
||||||
*/
|
*/
|
||||||
public function check(array $inputs, string $key): void
|
public function check(array $inputs, string $key): void
|
||||||
{
|
{
|
||||||
if (!isset($inputs[$key]))
|
if (!isset($inputs[$key]))
|
||||||
throw new ValidationException($this->override_message ?? "Missing input '$key'.", $key);
|
throw new InvalidInputException($this->override_message ?? "Missing input '$key'.", $key);
|
||||||
|
|
||||||
if ($this->min_length !== null && strlen($inputs[$key]) < $this->min_length)
|
if ($this->min_length !== null && strlen($inputs[$key]) < $this->min_length)
|
||||||
throw new ValidationException(
|
throw new InvalidInputException(
|
||||||
$this->override_message ?? "Use at least $this->min_length character(s).",
|
$this->override_message ?? "Use at least $this->min_length character(s).",
|
||||||
$key
|
$key
|
||||||
);
|
);
|
||||||
if ($this->max_length !== null && strlen($inputs[$key]) > $this->max_length)
|
if ($this->max_length !== null && strlen($inputs[$key]) > $this->max_length)
|
||||||
throw new ValidationException(
|
throw new InvalidInputException(
|
||||||
$this->override_message ?? "Use at most $this->max_length character(s).",
|
$this->override_message ?? "Use at most $this->max_length character(s).",
|
||||||
$key
|
$key
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,13 +2,16 @@
|
||||||
|
|
||||||
namespace com\fwdekker\deathnotifier\validator;
|
namespace com\fwdekker\deathnotifier\validator;
|
||||||
|
|
||||||
use Exception;
|
use com\fwdekker\deathnotifier\MalformedRequestException;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thrown if an action could not be handled because an input is invalid.
|
* Thrown if a client-specified input is invalid.
|
||||||
|
*
|
||||||
|
* This is an exception, not an error, so it indicates that the client that sent the request did something wrong.
|
||||||
*/
|
*/
|
||||||
class ValidationException extends Exception
|
class InvalidInputException extends MalformedRequestException
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var string|null the input element that caused the exception, or `null` if no such element could be identified
|
* @var string|null the input element that caused the exception, or `null` if no such element could be identified
|
||||||
|
@ -22,10 +25,11 @@ class ValidationException extends Exception
|
||||||
* @param string $message the message to show to the user
|
* @param string $message the message to show to the user
|
||||||
* @param string|null $target the input element that caused the exception, or `null` if no such element could be
|
* @param string|null $target the input element that caused the exception, or `null` if no such element could be
|
||||||
* identified
|
* identified
|
||||||
|
* @param Throwable|null $previous the throwable that caused this one
|
||||||
*/
|
*/
|
||||||
public function __construct(string $message, ?string $target = null)
|
public function __construct(string $message, ?string $target = null, ?Throwable $previous = null)
|
||||||
{
|
{
|
||||||
parent::__construct($message);
|
parent::__construct($message, previous: $previous);
|
||||||
|
|
||||||
$this->target = $target;
|
$this->target = $target;
|
||||||
}
|
}
|
|
@ -14,11 +14,11 @@ class IsEmailRule extends Rule
|
||||||
* @param array<int|string, mixed> $inputs the list of inputs in which the value at `key` should be checked
|
* @param array<int|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
|
* @param string $key the key in `inputs` of the input to check
|
||||||
* @return void if `$inputs[$key]` is an email address
|
* @return void if `$inputs[$key]` is an email address
|
||||||
* @throws ValidationException if `$inputs[$key]` is not set or is not an email address
|
* @throws InvalidInputException if `$inputs[$key]` is not set or is not an email address
|
||||||
*/
|
*/
|
||||||
public function check(array $inputs, string $key): void
|
public function check(array $inputs, string $key): void
|
||||||
{
|
{
|
||||||
if (!isset($inputs[$key]) || !filter_var($inputs[$key], FILTER_VALIDATE_EMAIL))
|
if (!isset($inputs[$key]) || !filter_var($inputs[$key], FILTER_VALIDATE_EMAIL))
|
||||||
throw new ValidationException($this->override_message ?? "Enter a valid email address.", $key);
|
throw new InvalidInputException($this->override_message ?? "Enter a valid email address.", $key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,12 +33,12 @@ class IsEqualToRule extends Rule
|
||||||
* @param array<int|string, mixed> $inputs the list of inputs in which the value at `key` should be checked
|
* @param array<int|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
|
* @param string $key the key in `inputs` of the input to check
|
||||||
* @return void if `$inputs[$key]` equals `$expected`
|
* @return void if `$inputs[$key]` equals `$expected`
|
||||||
* @throws ValidationException if `$inputs[$key]` is not set or does not equal `$expected`
|
* @throws InvalidInputException if `$inputs[$key]` is not set or does not equal `$expected`
|
||||||
*/
|
*/
|
||||||
public function check(array $inputs, string $key): void
|
public function check(array $inputs, string $key): void
|
||||||
{
|
{
|
||||||
if (!isset($inputs[$key]) || $inputs[$key] !== $this->expected)
|
if (!isset($inputs[$key]) || $inputs[$key] !== $this->expected)
|
||||||
throw new ValidationException(
|
throw new InvalidInputException(
|
||||||
$this->override_message ?? "Inputs '$key' should equal '$this->expected'.",
|
$this->override_message ?? "Inputs '$key' should equal '$this->expected'.",
|
||||||
$key
|
$key
|
||||||
);
|
);
|
||||||
|
|
|
@ -14,11 +14,11 @@ class IsNotBlankRule extends Rule
|
||||||
* @param array<int|string, mixed> $inputs the list of inputs in which the value at `key` should be checked
|
* @param array<int|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
|
* @param string $key the key in `inputs` of the input to check
|
||||||
* @return void if `trim($inputs[$key])` is not an empty string
|
* @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
|
* @throws InvalidInputException if `$inputs[$key]` is not set or if `trim($inputs[$key])` is an empty string
|
||||||
*/
|
*/
|
||||||
public function check(array $inputs, string $key): void
|
public function check(array $inputs, string $key): void
|
||||||
{
|
{
|
||||||
if (!isset($inputs[$key]) || trim($inputs[$key]) === "")
|
if (!isset($inputs[$key]) || trim($inputs[$key]) === "")
|
||||||
throw new ValidationException($this->override_message ?? "Use at least one character.", $key);
|
throw new InvalidInputException($this->override_message ?? "Use at least one character.", $key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,12 +14,12 @@ class IsNotSetRule extends Rule
|
||||||
* @param array<int|string, mixed> $inputs the list of inputs in which the value at `key` should be checked
|
* @param array<int|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
|
* @param string $key the key in `inputs` of the input to check
|
||||||
* @return void if `$inputs[$key]` is not set
|
* @return void if `$inputs[$key]` is not set
|
||||||
* @throws ValidationException if `$inputs[$key]` is set
|
* @throws InvalidInputException if `$inputs[$key]` is set
|
||||||
*/
|
*/
|
||||||
public function check(array $inputs, string $key): void
|
public function check(array $inputs, string $key): void
|
||||||
{
|
{
|
||||||
if (isset($inputs[$key]))
|
if (isset($inputs[$key]))
|
||||||
throw new ValidationException(
|
throw new InvalidInputException(
|
||||||
$this->override_message ?? "Field '" . htmlentities($key) . "' must not be set.",
|
$this->override_message ?? "Field '" . htmlentities($key) . "' must not be set.",
|
||||||
$key
|
$key
|
||||||
);
|
);
|
||||||
|
|
|
@ -14,12 +14,12 @@ class IsSetRule extends Rule
|
||||||
* @param array<int|string, mixed> $inputs the list of inputs in which the value at `key` should be checked
|
* @param array<int|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
|
* @param string $key the key in `inputs` of the input to check
|
||||||
* @return void if `$inputs[$key]` is set
|
* @return void if `$inputs[$key]` is set
|
||||||
* @throws ValidationException if `$inputs[$key]` is not set
|
* @throws InvalidInputException if `$inputs[$key]` is not set
|
||||||
*/
|
*/
|
||||||
public function check(array $inputs, string $key): void
|
public function check(array $inputs, string $key): void
|
||||||
{
|
{
|
||||||
if (!isset($inputs[$key]))
|
if (!isset($inputs[$key]))
|
||||||
throw new ValidationException(
|
throw new InvalidInputException(
|
||||||
$this->override_message ?? "Field '" . htmlentities($key) . "' must be set.",
|
$this->override_message ?? "Field '" . htmlentities($key) . "' must be set.",
|
||||||
$key
|
$key
|
||||||
);
|
);
|
||||||
|
|
|
@ -35,7 +35,7 @@ abstract class Rule
|
||||||
* @param array<int|string, mixed> $inputs the list of inputs in which the value at `key` should be checked
|
* @param array<int|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
|
* @param string $key the key in `inputs` of the input to check
|
||||||
* @return void if the rule holds
|
* @return void if the rule holds
|
||||||
* @throws ValidationException if the rule does not hold
|
* @throws InvalidInputException if the rule does not hold
|
||||||
*/
|
*/
|
||||||
public abstract function check(array $inputs, string $key): void;
|
public abstract function check(array $inputs, string $key): void;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue