user_list = $user_list; $this->email_queue = $email_queue; } /** * Changes the user's email address and sends the user a verification link. * * Requires that the user is logged in and that a valid CSRF token is present. * * @param array $inputs `"token": string`: a valid CSRF token, `"email": string`: the new email * address * @return null * @throws InvalidTypeException if any of the inputs has the incorrect type * @throws InvalidValueException if the user is not logged in, if no valid CSRF token is present, if the email * address is invalid, if the email address is not different, or if the email address is already used * @throws UnexpectedException if the current user has been deleted * @noinspection PhpDocRedundantThrowsInspection can be thrown through {@see TrackingList::transaction()} */ public function handle(array $inputs): mixed { (new LoginValidator(validate_logged_in: true))->check($_SESSION); (new RuleSet([ "token" => [new IsValidCsrfTokenRule()], "email" => [new IsEmailRule()], ]))->check($inputs); $this->user_list->transaction(function () use ($inputs) { $user_data = $this->user_list->get_user_by_uuid($_SESSION["uuid"]); if ($user_data === null) throw new UnexpectedException("Failed to retrieve user data. Please refresh the page and try again."); if ($inputs["email"] === $user_data["email"]) throw new InvalidValueException("That is already your email address.", "email"); if ($this->user_list->has_user_with_email($inputs["email"])) throw new InvalidValueException("That email address is already in use by someone else.", "email"); $token = $this->user_list->set_email($_SESSION["uuid"], $inputs["email"]); $this->email_queue->queue_emails([ new ChangeEmailFromEmail($user_data["email"], $inputs["email"]), new ChangeEmailToEmail($inputs["email"], $user_data["email"], $token) ]); }); return null; } } /** * An email informing a user that their email has been changed and needs to be verified, sent to the user's old email * address. * * @see ChangeEmailAction */ class ChangeEmailFromEmail extends Email { /** * A string identifying the type of email. */ public const TYPE = "changed-email-from"; /** * @var string the new email address */ public readonly string $new_email; /** * Constructs a new `ChangeEmailFromEmail`. * * @param string $recipient the intended recipient of the email * @param string $new_email the new email address */ public function __construct(string $recipient, string $new_email) { parent::__construct($this::TYPE, $recipient); $this->new_email = $new_email; } public function get_subject(): string { return "Your email address has been changed"; } public function get_body(): string { $base_path = Config::get("server.base_path"); return "You changed the email address of your Death Notifier account from $this->recipient to $this->new_email. " . "Until you verify your email address, you will not receive any notifications on either email address. " . "Check the inbox of $this->new_email for a verification link." . "\n\n" . "If you did not request a change of email address, log in at the Death Notifier website, change your " . "password, and change your email address back." . "\n\n" . $base_path; } } /** * An email informing a user that their email has been changed and needs to be verified, sent to the user's new email * address that needs to be verified. * * @see ChangeEmailAction */ class ChangeEmailToEmail extends Email { /** * A string identifying the type of email. */ public const TYPE = "changed-email-to"; /** * @var string the old email address */ public readonly string $old_email; /** * @var string the token to verify the email address with */ public readonly string $token; /** * Constructs a new `ChangeEmailEmail`. * * @param string $recipient the intended recipient of the email * @param string $old_email the old email address * @param string $token the token to verify the email address with */ public function __construct(string $recipient, string $old_email, string $token) { parent::__construct($this::TYPE, $recipient); $this->old_email = $old_email; $this->token = $token; } public function get_subject(): string { return "Verify your new email address"; } public function get_body(): string { $base_path = Config::get("server.base_path"); $verify_path = "$base_path?action=verify-email&email=" . rawurlencode($this->recipient) . "&token=$this->token"; return "You changed the email address of your Death Notifier account from $this->old_email to $this->recipient. " . "Until you verify your email address, you will not receive any notifications. " . "You can verify your new email address by clicking the link below. " . "This link will expire after " . UserList::MINUTES_VALID_VERIFICATION . " minutes." . "\n" . "Verify: $verify_path" . "\n\n" . $base_path; } }