diff --git a/projects/packages/account-protection/src/class-account-protection.php b/projects/packages/account-protection/src/class-account-protection.php index 2db8aaf73582d..72900f144154f 100644 --- a/projects/packages/account-protection/src/class-account-protection.php +++ b/projects/packages/account-protection/src/class-account-protection.php @@ -43,6 +43,8 @@ public function __construct( ?Modules $modules = null, ?Password_Detection $pass /** * Initializes the configurations needed for the account protection module. + * + * @return void */ public function init(): void { $this->register_hooks(); @@ -54,6 +56,8 @@ public function init(): void { /** * Register hooks for module activation and environment validation. + * + * @return void */ private function register_hooks(): void { // Account protection activation/deactivation hooks @@ -67,24 +71,24 @@ private function register_hooks(): void { /** * Register hooks for runtime operations. + * + * @return void */ private function register_runtime_hooks(): void { // Validate password after successful login add_action( 'wp_authenticate_user', array( $this->password_detection, 'login_form_password_detection' ), 10, 2 ); + // Handle password detection login failure + add_action( 'wp_login_failed', array( $this->password_detection, 'handle_password_detection_validation_error' ), 10, 2 ); + // Add password detection flow add_action( 'login_form_password-detection', array( $this->password_detection, 'render_page' ), 10, 2 ); - - // Remove password detection usermeta after password reset and on profile password update - add_action( 'after_password_reset', array( $this->password_detection, 'delete_usermeta_after_password_reset' ), 10, 2 ); - add_action( 'profile_update', array( $this->password_detection, 'delete_usermeta_on_profile_update' ), 10, 2 ); - - // Register AJAX resend password reset email action - add_action( 'wp_ajax_resend_password_reset', array( $this->password_detection, 'ajax_resend_password_reset_email' ) ); } /** * Activate the account protection on module activation. + * + * @return void */ public function on_account_protection_activation(): void { // Activation logic can be added here @@ -92,11 +96,11 @@ public function on_account_protection_activation(): void { /** * Deactivate the account protection on module deactivation. + * + * @return void */ public function on_account_protection_deactivation(): void { - // Remove password detection user meta on deactivation - // TODO: Run on Jetpack and Protect deactivation - $this->password_detection->delete_all_usermeta(); + // Deactivation logic can be added here } /** @@ -104,7 +108,7 @@ public function on_account_protection_deactivation(): void { * * @return bool */ - public function is_enabled() { + public function is_enabled(): bool { return $this->modules->is_active( self::ACCOUNT_PROTECTION_MODULE_NAME ); } @@ -113,7 +117,7 @@ public function is_enabled() { * * @return bool */ - public function enable() { + public function enable(): bool { // Return true if already enabled. if ( $this->is_enabled() ) { return true; diff --git a/projects/packages/account-protection/src/class-config.php b/projects/packages/account-protection/src/class-config.php new file mode 100644 index 0000000000000..99d461441752a --- /dev/null +++ b/projects/packages/account-protection/src/class-config.php @@ -0,0 +1,19 @@ +is_connected(); + + if ( ! $blog_id || ! $is_connected ) { + return false; + } + + $body = array( + 'user_login' => $user->user_login, + 'user_email' => $user->user_email, + 'code' => $auth_code, + ); + + $response = Client::wpcom_json_api_request_as_blog( + sprintf( '/sites/%d/jetpack-protect-send-verification-code', $blog_id ), + '2', + array( + 'method' => 'POST', + ), + $body, + 'wpcom' + ); + + $response_code = wp_remote_retrieve_response_code( $response ); + if ( is_wp_error( $response ) || 200 !== $response_code || empty( $response['body'] ) ) { + return false; + } + + $body = json_decode( wp_remote_retrieve_body( $response ), true ); + + return $body['email_sent'] ?? false; + } + + /** + * Resend email attempts. + * + * @param \WP_User $user The user. + * @param array $transient_data The transient data. + * @param string $token The token. + * + * @return bool True if the email was resent successfully, false otherwise. + */ + public function resend_auth_email( \WP_User $user, array $transient_data, string $token ): bool { + if ( $transient_data['resend_attempts'] >= Config::MAX_RESEND_ATTEMPTS ) { + return false; + } + + $auth_code = $this->generate_auth_code(); + $transient_data['auth_code'] = $auth_code; + + if ( ! $this->api_send_auth_email( $user, $auth_code ) ) { + return false; + } + + ++$transient_data['resend_attempts']; + + if ( ! set_transient( Config::TRANSIENT_PREFIX . "_{$token}", $transient_data, Config::EMAIL_SENT_EXPIRATION ) ) { + return false; + } + + return true; + } + + /** + * Generate an auth code. + * + * @return string The generated auth code. + */ + public function generate_auth_code(): string { + return (string) wp_rand( 100000, 999999 ); + } + + /** + * Mask an email address like d*****@g*****.com. + * + * @param string $email The email address to mask. + * + * @return string The masked email address. + */ + public function mask_email_address( string $email ): string { + $parts = explode( '@', $email ); + $name = substr( $parts[0], 0, 1 ) . str_repeat( '*', strlen( $parts[0] ) - 1 ); + $domain_parts = explode( '.', $parts[1] ); + $domain = substr( $domain_parts[0], 0, 1 ) . str_repeat( '*', strlen( $domain_parts[0] ) - 1 ); + + return "{$name}@{$domain}.{$domain_parts[1]}"; + } +} diff --git a/projects/packages/account-protection/src/class-password-detection.php b/projects/packages/account-protection/src/class-password-detection.php index ca195ed1207e6..53e035098582e 100644 --- a/projects/packages/account-protection/src/class-password-detection.php +++ b/projects/packages/account-protection/src/class-password-detection.php @@ -7,38 +7,33 @@ namespace Automattic\Jetpack\Account_Protection; -use Automattic\Jetpack\Connection\Client; -use Automattic\Jetpack\Connection\Manager as Connection_Manager; - /** * Class Password_Detection */ class Password_Detection { - const PASSWORD_DETECTION_USER_META_KEY = 'jetpack_account_protection_password_status'; - /** - * Password reset email dependency. + * Email service dependency. * - * @var Password_Reset_Email + * @var Email_Service */ - private $password_reset_email; + private $email_service; /** - * Password_Detection constructor. + * Validation service dependency. * - * @param ?Password_Reset_Email $password_reset_email Password reset email instance. + * @var Validation_Service */ - public function __construct( ?Password_Reset_Email $password_reset_email = null ) { - $this->password_reset_email = $password_reset_email ?? new Password_Reset_Email(); - } + private $validation_service; /** - * Redirect to the password detection page. + * Password_Detection constructor. * - * @return string The URL to redirect to. + * @param ?Email_Service $email_service Email service instance. + * @param ?Validation_Service $validation_service Validation service instance. */ - public function password_detection_redirect(): string { - return home_url( '/wp-login.php?action=password-detection' ); + public function __construct( ?Email_Service $email_service = null, ?Validation_Service $validation_service = null ) { + $this->email_service = $email_service ?? new Email_Service(); + $this->validation_service = $validation_service ?? new Validation_Service(); } /** @@ -46,343 +41,295 @@ public function password_detection_redirect(): string { * * @param \WP_User|\WP_Error $user The user or error object. * @param string $password The password. + * * @return \WP_User|\WP_Error The user object. */ public function login_form_password_detection( $user, string $password ) { - // Check if the user is already a WP_Error object - if ( is_wp_error( $user ) ) { + if ( is_wp_error( $user ) || ! $this->user_requires_protection( $user, $password ) ) { return $user; } - // Ensure the password is correct for this user - if ( ! wp_check_password( $password, $user->user_pass, $user->ID ) ) { - return $user; - } + if ( $this->validation_service->is_weak_password( $password ) ) { + // TODO: Every time the user logs in we generate a new token based transient. This might not be ideal. + $transient = $this->generate_and_store_transient_data( $user->ID ); - if ( ! $this->validate_password( $password ) ) { - // TODO: Ensure usermeta is always up to date - $this->update_usermeta( $user->ID, 'unsafe' ); + $email_sent = $this->email_service->api_send_auth_email( $user, $transient['auth_code'] ); + if ( ! $email_sent ) { + $this->set_transient_error( $user->ID, __( 'Failed to send authentication email. Please try again.', 'jetpack-account-protection' ) ); + } - // Redirect to the password detection page - add_filter( 'login_redirect', array( $this, 'password_detection_redirect' ), 10, 3 ); - } else { - $this->update_usermeta( $user->ID, 'safe' ); + return new \WP_Error( + Config::ERROR_CODE, + Config::ERROR_MESSAGE, + array( 'token' => $transient['token'] ) + ); } return $user; } /** - * Render password detection page. + * Handle password detection validation error. * - * @return never + * @param string $username The username. + * @param \WP_Error $error The error object. + * + * @return void */ - public function render_page() { - // Restrict direct access to logged in users - $current_user = wp_get_current_user(); - if ( 0 === $current_user->ID ) { - wp_safe_redirect( wp_login_url() ); + public function handle_password_detection_validation_error( string $username, \WP_Error $error ): void { + if ( isset( $error->errors['password_detection_validation_error'] ) ) { + $token = $error->get_error_data()['token']; + wp_safe_redirect( $this->get_redirect_url( $token ) ); exit; } + } - // Restrict direct access to users with unsafe passwords - $user_password_status = $this->get_usermeta( $current_user->ID ); - if ( ! $user_password_status || 'safe' === $user_password_status ) { + /** + * Render password detection page. + * + * @return never + */ + public function render_page() { + if ( is_user_logged_in() ) { wp_safe_redirect( admin_url() ); exit; } - // Use a transient to track email sent status - $transient_key = 'password_reset_email_sent_' . $current_user->ID; - $email_sent_flag = get_transient( $transient_key ); + $token = isset( $_GET['token'] ) ? sanitize_text_field( wp_unslash( $_GET['token'] ) ) : null; + $transient_data = get_transient( Config::TRANSIENT_PREFIX . "_{$token}" ); + if ( ! $transient_data ) { + $this->redirect_to_login(); + } - // Initialize template variables - $reset = false; - $context = 'Your current password was found in a public leak, which means your account might be at risk.'; - $error = ''; + $user_id = $transient_data['user_id'] ?? null; + $user = $user_id ? get_user_by( 'ID', $user_id ) : null; + if ( ! $user instanceof \WP_User ) { + $this->redirect_to_login(); + } add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_styles' ) ); - // Handle reset_password_action form submission - if ( isset( $_POST['reset-password'] ) ) { - $reset = true; - - // Verify nonce - if ( isset( $_POST['_wpnonce_reset_password'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce_reset_password'] ) ), 'reset_password_action' ) ) { - // Send password reset email - if ( ! $email_sent_flag ) { - $email_sent = $this->password_reset_email->send(); - if ( $email_sent ) { - // Set transient to mark the email as sent - set_transient( $transient_key, true, 15 * MINUTE_IN_SECONDS ); - } else { - $error = 'email_send_error'; + // Handle resend email request + if ( isset( $_GET['resend_email'] ) && $_GET['resend_email'] === '1' ) { + if ( isset( $_GET['_wpnonce'] ) + && wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), 'resend_email_nonce' ) + ) { + $email_resent = $this->email_service->resend_auth_email( $user, $transient_data, $token ); + if ( ! $email_resent ) { + $message = __( 'Failed to resend authentication email. Please try again.', 'jetpack-account-protection' ); + + if ( $transient_data['resend_attempts'] >= Config::MAX_RESEND_ATTEMPTS ) { + $message = __( 'Resend limit exceeded. Please try again later.', 'jetpack-account-protection' ); } + + $this->set_transient_error( $user->ID, $message ); } - add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_resend_password_reset_scripts' ) ); + wp_safe_redirect( $this->get_redirect_url( $token ) ); + exit; } else { - $error = 'reset_passowrd_nonce_verification_error'; + $this->set_transient_error( $user->ID, __( 'Resend nonce verification failed. Please try again.', 'jetpack-account-protection' ) ); } + } - // Handle proceed_action form submission - } elseif ( isset( $_POST['proceed'] ) ) { - $reset = true; + // Handle verify form submission + if ( isset( $_POST['verify'] ) ) { + if ( ! empty( $_POST['_wpnonce_verify'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce_verify'] ) ), 'verify_action' ) ) { + $user_input = isset( $_POST['user_input'] ) ? sanitize_text_field( wp_unslash( $_POST['user_input'] ) ) : null; - // Verify nonce - if ( isset( $_POST['_wpnonce_proceed'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce_proceed'] ) ), 'proceed_action' ) ) { - wp_safe_redirect( admin_url() ); - exit; + $this->handle_auth_form_submission( $user, $token, $transient_data['auth_code'] ?? null, $user_input ); } else { - $error = 'proceed_nonce_verification_error'; + $this->set_transient_error( $user->ID, __( 'Verify nonce verification failed. Please try again.', 'jetpack-account-protection' ) ); } } - $this->render_content( $reset, $context, $error, $this->password_reset_email->mask_email_address( $current_user->user_email ) ); + $this->render_content( $user, $token ); exit; } /** - * Enqueue the resend password reset email scripts. + * Render content for password detection page. * - * @return void - */ - public function enqueue_resend_password_reset_scripts(): void { - wp_enqueue_script( 'resend-password-reset', plugin_dir_url( __FILE__ ) . 'js/resend-password-reset.js', array( 'jquery' ), Account_Protection::PACKAGE_VERSION, true ); - - // Pass AJAX URL and nonce to the script - wp_localize_script( - 'resend-password-reset', - 'ajaxObject', - array( - 'ajax_url' => admin_url( 'admin-ajax.php' ), - 'nonce' => wp_create_nonce( 'resend_password_reset_nonce' ), - ) - ); - } - - /** - * Enqueue the password detection page styles. + * @param \WP_User $user The user. + * @param string $token The token. * * @return void */ - public function enqueue_styles(): void { - wp_enqueue_style( - 'password-detection-styles', - plugin_dir_url( __FILE__ ) . 'css/password-detection.css', - array(), - Account_Protection::PACKAGE_VERSION - ); - } + public function render_content( \WP_User $user, string $token ): void { + $transient_key = Config::TRANSIENT_PREFIX . "_error_{$user->ID}"; + $error_message = get_transient( $transient_key ); + delete_transient( $transient_key ); - /** - * Run AJAX request to resend password reset email. - */ - public function ajax_resend_password_reset_email() { - // Verify the nonce for security - check_ajax_referer( 'resend_password_reset_nonce', 'security' ); - - // Check if the user is logged in - if ( ! is_user_logged_in() ) { - wp_send_json_error( array( 'message' => 'User not authenticated' ) ); - } - - // Resend the email - $email_sent = $this->password_reset_email->send(); - if ( $email_sent ) { - wp_send_json_success( array( 'message' => 'Resend successful.' ) ); - } else { - wp_send_json_error( array( 'message' => 'Resend failed. ' ) ); - } + defined( 'ABSPATH' ) || exit; + ?> + + + + + + <?php esc_html_e( 'Jetpack - Secure Your Account', 'jetpack-account-protection' ); ?> + + + +
+ +

+

+

+ email_service->mask_email_address( $user->user_email ) ) + ); + ?> +

+
+
+ + + +
+
+

+ + + + +

+ +

+ +
+ + + + user_pass, $user->ID ); } /** - * Check if the password is in the list of common/compromised passwords. + * Generate and store a consolidated transient for the user. + * + * @param int $user_id The user ID. * - * @param string $password The password to check. - * @return bool|\WP_Error True if the password is in the list of common/compromised passwords, false otherwise. + * @return array An array of the generated token and auth code. */ - public function check_weak_passwords( string $password ) { - $api_url = '/jetpack-protect-weak-password'; - - $is_connected = ( new Connection_Manager() )->is_connected(); - - if ( ! $is_connected ) { - return new \WP_Error( 'site_not_connected' ); - } - - // Hash pass with sha1, and pass first 5 characters to the API - $hashed_password = sha1( $password ); - $password_prefix = substr( $hashed_password, 0, 5 ); - - $response = Client::wpcom_json_api_request_as_blog( - $api_url . '/' . $password_prefix, - '2', - array( 'method' => 'GET' ), - null, - 'wpcom' + private function generate_and_store_transient_data( int $user_id ): array { + $token = wp_generate_password( 32, false, false ); + $auth_code = $this->email_service->generate_auth_code(); + + $data = array( + 'user_id' => $user_id, + 'auth_code' => $auth_code, + 'resend_attempts' => 0, ); - $response_code = wp_remote_retrieve_response_code( $response ); - - if ( is_wp_error( $response ) || 200 !== $response_code || empty( $response['body'] ) ) { - return new \WP_Error( 'failed_fetching_weak_passwords', 'Failed to fetch weak passwords from the server', array( 'status' => $response_code ) ); + $transient_set = set_transient( Config::TRANSIENT_PREFIX . "_{$token}", $data, Config::EMAIL_SENT_EXPIRATION ); + if ( ! $transient_set ) { + $this->set_transient_error( $user_id, __( 'Failed to set transient data. Please try again.', 'jetpack-account-protection' ) ); } - $body = json_decode( wp_remote_retrieve_body( $response ), true ); - - // Check if the password is in the list of common/compromised passwords - $password_suffix = substr( $hashed_password, 5 ); - if ( in_array( $password_suffix, $body['compromised'] ?? array(), true ) ) { - return true; - } - - return false; + return array( + 'token' => $token, + 'auth_code' => $auth_code, + ); } /** - * Get the password detection usermeta. + * Redirect to the login page. * - * @param int $user_id The user ID. + * @return never */ - public function get_usermeta( int $user_id ) { - return get_user_meta( $user_id, self::PASSWORD_DETECTION_USER_META_KEY, true ); + private function redirect_to_login() { + wp_safe_redirect( wp_login_url() ); + exit; } /** - * Update the password detection usermeta. + * Get redirect URL. * - * @param int $user_id The user ID. - * @param string $setting The password detection setting. - */ - public function update_usermeta( int $user_id, string $setting ) { - update_user_meta( $user_id, self::PASSWORD_DETECTION_USER_META_KEY, $setting ); - } - - /** - * Delete password detection usermeta for all users. - */ - public function delete_all_usermeta() { - $users = get_users(); - foreach ( $users as $user ) { - $this->delete_usermeta( $user->ID ); - } - } - - /** - * Delete the password detection usermeta. + * @param string $token The token. * - * @param int $user_id The user ID. + * @return string The redirect URL. */ - public function delete_usermeta( int $user_id ) { - delete_user_meta( $user_id, self::PASSWORD_DETECTION_USER_META_KEY ); + private function get_redirect_url( string $token ): string { + return home_url( '/wp-login.php?action=password-detection&token=' . $token ); } /** - * Delete the password detection usermeta after password reset. + * Handle auth form submission. * - * @param \WP_User $user The user object. + * @param \WP_User $user The current user. + * @param string $token The token. + * @param string $auth_code The expected auth code. + * @param string $user_input The user input. + * + * @return void */ - public function delete_usermeta_after_password_reset( \WP_User $user ) { - $this->delete_usermeta( $user->ID ); + private function handle_auth_form_submission( \WP_User $user, string $token, string $auth_code, string $user_input ): void { + if ( $auth_code && $auth_code === $user_input ) { + // TODO: Ensure all transient are also removed on module and/or plugin deactivation + delete_transient( Config::TRANSIENT_PREFIX . "_{$token}" ); + wp_set_auth_cookie( $user->ID, true ); + // TODO: Notify user to update their password/redirect to password update page + wp_safe_redirect( admin_url() ); + exit; + } else { + $this->set_transient_error( $user->ID, __( 'Authentication code verification failed. Please try again.', 'jetpack-account-protection' ) ); + } } /** - * Delete the password detection usermeta on profile password update. + * Set a transient error message. * - * @param int $user_id The user ID. + * @param int $user_id The user ID. + * @param string $message The error message. + * @param int $expiration The expiration time in seconds. + * + * @return void */ - public function delete_usermeta_on_profile_update( int $user_id ) { - if ( - ! empty( $_POST['_wpnonce'] ) && - wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), 'update-user_' . $user_id ) - ) { - if ( isset( $_POST['pass1'] ) && ! empty( $_POST['pass1'] ) ) { - $this->delete_usermeta( $user_id ); - } - } + private function set_transient_error( int $user_id, string $message, int $expiration = 60 ): void { + set_transient( Config::TRANSIENT_PREFIX . "_error_{$user_id}", esc_html( $message ), $expiration ); } /** - * Render content for password detection page. + * Enqueue the password detection page styles. * - * @param bool $reset Whether the user is resetting their password. - * @param string $context The context for the password detection page. - * @param string $error The error message to display. - * @param string $masked_email The masked email address. * @return void */ - public function render_content( bool $reset, string $context, string $error, string $masked_email ): void { - defined( 'ABSPATH' ) || exit; - ?> - - - - - - <?php echo esc_html( $reset ? 'Jetpack - Stay Secure' : 'Jetpack - Secure Your Account' ); ?> - - - -
- -

- -

- - -

We've encountered an issue verifying your request to proceed without updating your password.

- -

- - While attempting to send a verification email to , an error occurred. -

- - -

Don't worry - To keep your account safe, we've sent a verification email to . After that, we'll guide you through updating your password.

- -

Please check your inbox and click the link to verify it's you. Alternatively, you can update your password from your account profile.

-

- Didn't get the email? - Resend email -

- -

-

It is highly recommended that you update your password.

-
-
- - -
-
- - -
-
-

Learn more about the risks of using weak passwords and how to protect your account.

- -
- - - - is_connected(); + if ( ! $is_connected ) { + return false; + } + + $hashed_password = sha1( $password ); + $password_prefix = substr( $hashed_password, 0, 5 ); + + $response = Client::wpcom_json_api_request_as_blog( + $api_url . '/' . $password_prefix, + '2', + array( 'method' => 'GET' ), + null, + 'wpcom' + ); + + $response_code = wp_remote_retrieve_response_code( $response ); + + if ( is_wp_error( $response ) || 200 !== $response_code || empty( $response['body'] ) ) { + return false; + } + + $body = json_decode( wp_remote_retrieve_body( $response ), true ); + + $password_suffix = substr( $hashed_password, 5 ); + if ( in_array( $password_suffix, $body['compromised'] ?? array(), true ) ) { + return true; + } + + if ( in_array( $password_suffix, $body['common'] ?? array(), true ) ) { + return true; + } + + return false; + } +} diff --git a/projects/packages/account-protection/src/css/password-detection.css b/projects/packages/account-protection/src/css/password-detection.css index d1ec425dd5da3..d568caa5b0017 100644 --- a/projects/packages/account-protection/src/css/password-detection.css +++ b/projects/packages/account-protection/src/css/password-detection.css @@ -33,17 +33,29 @@ height: 36px; cursor: pointer; width: 100%; + border-radius: 2px; } -.action-reset { - margin-top: 10px; - background-color: #0000EE; - border: 1px solid #0000EE; - color: #fff; +.action-input { + height: 30px; + cursor: pointer; + width: 412px; + text-indent: 8px; + + &::placeholder { + font-size: 13px; } - -.action-proceed { - background-color: #fff; - border: 1px solid #0000EE; - color: #0000EE; -} \ No newline at end of file +} + +.action-verify { + margin-top: 10px; + background-color: #0000EE; + border: 1px solid #0000EE; + color: #fff; + font-size: 13px; +} + +.email-status, +.error-message { + text-align: center; +} diff --git a/projects/packages/account-protection/src/js/resend-password-reset.js b/projects/packages/account-protection/src/js/resend-password-reset.js deleted file mode 100644 index 2e0cdf8a4ab0a..0000000000000 --- a/projects/packages/account-protection/src/js/resend-password-reset.js +++ /dev/null @@ -1,71 +0,0 @@ -/* global jQuery, ajaxObject */ -( function ( $ ) { - $( document ).ready( function () { - const attemptLimit = 3; - let attempts = 0; - - $( '#resend-password-reset' ).on( 'click', function ( e ) { - e.preventDefault(); // Prevent the default action - - const message = $( '#resend-password-reset-message' ); - const button = $( this ); - - // Store the original text of the message - const originalMessageText = message.text(); - - // Update message and hide button while resending - message.text( 'Resending email...' ); - button.hide(); - - attempts++; - - // Perform the AJAX request - $.ajax( { - url: ajaxObject.ajax_url, - type: 'POST', - data: { - action: 'resend_password_reset', - security: ajaxObject.nonce, - }, - success: function ( response ) { - if ( response.success ) { - // Show success message - message.text( response.data.message ).show(); - - // Hide the status message and show the button after 5 seconds - setTimeout( function () { - let messageText = originalMessageText; - if ( attempts < attemptLimit ) { - button.show(); - } else { - messageText += 'Please try again later.'; - } - message.text( messageText ).show(); - }, 5000 ); - } else { - // Show error message - let messageText = 'An error occurred. '; - if ( attempts < attemptLimit ) { - button.text( 'Please try again' ).show(); - } else { - messageText += 'Please contact support.'; // TODO: Add support redirect - } - - message.text( messageText ).show(); - } - }, - error: function () { - // Show error message - let messageText = 'An error occurred. '; - if ( attempts < attemptLimit ) { - button.text( 'Please try again' ).show(); - } else { - messageText += 'Please contact support.'; // TODO: Add support redirect - } - - message.text( messageText ).show(); - }, - } ); - } ); - } ); -} )( jQuery );