user_list = $user_list; $this->email_queue = $email_queue; } /** * Resets the email verification process and sends a new verification email. * * Requires that the user is logged in and that a valid CSRF token is present. * * @param array $inputs `"token": string`: a valid CSRF token * @return null * @throws InvalidInputException if the user is logged out, if no valid CSRF token is present, if the email address * is already verified, or if a verification email was sent too recently * @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 SessionRuleSet(validate_logged_in: true))->check($_SESSION); (new RuleSet(["token" => [new IsValidCsrfTokenRule()]]))->check($inputs); $this->user_list->transaction(function () { $user_data = $this->user_list->get_user_by_uuid($_SESSION["uuid"]); if ($user_data === null) throw new UnexpectedException("Failed to retrieve user data. Refresh the page and try again."); if ($user_data["email_verification_token"] === null) throw new InvalidInputException("Your email address is already verified."); $minutes_left = Util::minutes_until_interval_elapsed( $user_data["email_verification_token_timestamp"], UserList::MINUTES_BETWEEN_VERIFICATION_EMAILS ); if ($minutes_left > 0) { throw new InvalidInputException( "A verification email was sent recently. " . "Please wait $minutes_left more minute(s) before requesting a new email." ); } $token = $this->user_list->set_email($_SESSION["uuid"], $user_data["email"]); $this->email_queue->queue_email(new ResendVerifyEmailEmail($user_data["email"], $token)); }); return null; } } /** * An email to help a user verify their email address in case they cannot use the original email. * * @see ResendVerifyEmailAction */ class ResendVerifyEmailEmail extends Email { /** * A string identifying the type of email. */ public const TYPE = "verify-email"; /** * @var string the token to verify the email address with */ public readonly string $token; /** * Constructs a new `ResendVerifyEmailEmail`. * * @param string $recipient the intended recipient of the email * @param string $token the token to verify the email address with */ public function __construct(string $recipient, string $token) { parent::__construct($this::TYPE, $recipient); $this->token = $token; } public function get_subject(): string { return "Verify your 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 requested a new verification link for your Death Notifier account. " . "You can verify your email address by clicking the link below. " . "This link will expire after " . UserList::MINUTES_VALID_VERIFICATION . " minutes. " . "Until you verify your email address, you will not receive any notifications." . "\n" . "Verify: $verify_path" . "\n\n" . $base_path; } }