Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Account Protection: Improve auth success flow #41727

Open
wants to merge 6 commits into
base: add/packages/account-protection-password-detection-error-notices
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
181 changes: 150 additions & 31 deletions projects/packages/account-protection/src/class-password-detection.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,13 @@ public function login_form_password_detection( $user, string $password ) {

$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' ) );
$this->set_transient_error(
$user->ID,
array(
'code' => 'email_send_error',
'message' => __( 'Failed to send authentication email. Please try again.', 'jetpack-account-protection' ),
)
);
}

return new \WP_Error(
Expand Down Expand Up @@ -149,21 +155,41 @@ public function render_page() {
&& 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 ( $email_resent ) {
$this->set_transient_success(
$user->ID,
array(
'code' => 'email_resend_success',
'message' => __( 'Authentication email resent successfully.', 'jetpack-account-protection' ),
)
);
} else {
$error = array(
'code' => 'email_resend_error',
'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' );
$error = array(
'code' => 'email_resend_limit_error',
'message' => __( 'Resend limit exceeded. Please try again later.', 'jetpack-account-protection' ),
);
}

$this->set_transient_error( $user->ID, $message );
$this->set_transient_error( $user->ID, $error );
}

$this->redirect_and_exit( $this->get_redirect_url( $token ) );
// @phan-suppress-next-line PhanPluginUnreachableCode This would fall through in unit tests otherwise.
return;
} else {
$this->set_transient_error( $user->ID, __( 'Resend nonce verification failed. Please try again.', 'jetpack-account-protection' ) );
$this->set_transient_error(
$user->ID,
array(
'code' => 'email_resend_nonce_error',
'message' => __( 'Resend nonce verification failed. Please try again.', 'jetpack-account-protection' ),
)
);
}
}

Expand All @@ -174,7 +200,13 @@ public function render_page() {

$this->handle_auth_form_submission( $user, $token, $transient_data['auth_code'] ?? null, $user_input );
} else {
$this->set_transient_error( $user->ID, __( 'Verify nonce verification failed. Please try again.', 'jetpack-account-protection' ) );
$this->set_transient_error(
$user->ID,
array(
'code' => 'verify_nonce_error',
'message' => __( 'Verify nonce verification failed. Please try again.', 'jetpack-account-protection' ),
)
);
}
}

Expand All @@ -190,9 +222,36 @@ public function render_page() {
* @return void
*/
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 );
$error_transient_key = Config::TRANSIENT_PREFIX . "_error_{$user->ID}";
$success_transient_key = Config::TRANSIENT_PREFIX . "_success_{$user->ID}";

$error_data = get_transient( $error_transient_key );
$success_data = get_transient( $success_transient_key );

delete_transient( $error_transient_key );
delete_transient( $success_transient_key );

$error_message = null;
$error_code = null;
if ( is_array( $error_data ) ) {
if ( isset( $error_data['message'] ) ) {
$error_message = $error_data['message'];
}
if ( isset( $error_data['code'] ) ) {
$error_code = $error_data['code'];
}
}

$success_message = null;
$success_code = null;
if ( is_array( $success_data ) ) {
if ( isset( $success_data['message'] ) ) {
$success_message = $success_data['message'];
}
if ( isset( $success_data['code'] ) ) {
$success_code = $success_data['code'];
}
}

defined( 'ABSPATH' ) || exit;
?>
Expand All @@ -207,12 +266,39 @@ public function render_content( \WP_User $user, string $token ): void {
<body class="password-detection-wrapper">
<div class="password-detection">
<?php require plugin_dir_path( __FILE__ ) . '/assets/jetpack-logo.svg'; ?>
<p class="password-detection-title"><?php esc_html_e( 'Verify your identity', 'jetpack-account-protection' ); ?></p>
<?php if ( $error_message ) : ?>
<div class="error notice-wrapper">
<p class="notice-message"><?php echo esc_html( $error_message ); ?></p>
</div>
<?php endif; ?>
<p class="password-detection-title"><?php echo $success_code === 'auth_code_success' ? esc_html__( 'Take action to stay secure', 'jetpack-account-protection' ) : esc_html__( 'Verify your identity', 'jetpack-account-protection' ); ?></p>
<?php if ( $error_message ) : ?>
<div class="error notice">
<p class="notice-message"><?php echo esc_html( $error_message ); ?></p>
</div>
<?php endif; ?>
<?php if ( $success_message ) : ?>
<div class="success notice">
<p class="notice-message"><?php echo esc_html( $success_message ); ?></p>
</div>
<?php endif; ?>
<?php if ( $success_code === 'auth_code_success' ) : ?>
<p><?php esc_html_e( "You're all set! You can now access your account.", 'jetpack-account-protection' ); ?></p>
<p><?php esc_html_e( 'Please keep in mind that your current password was found in a public leak, which means your account might be at risk. It is highly recommended that you update your password.', 'jetpack-account-protection' ); ?></p>
<div class="actions">
<a href="<?php echo esc_url( admin_url( 'profile.php#password' ) ); ?>" class="action action-update-password">
<?php esc_html_e( 'Create a new password', 'jetpack-account-protection' ); ?>
</a>
<a href="<?php echo esc_url( admin_url() ); ?>" class="action action-proceed">
<?php esc_html_e( 'Proceed without updating', 'jetpack-account-protection' ); ?>
</a>
</div>

<p>
<?php
printf(
/* translators: %s: Risks of using weak passwords link */
esc_html__( 'Learn more about the %1$s and how to protect your account.', 'jetpack-account-protection' ),
'<a class="risks-link" href="#" target="_blank" rel="noopener noreferrer">' . esc_html__( 'risks of using weak passwords', 'jetpack-account-protection' ) . '</a>' // TODO: Update this redirect URL once document exists
);
?>
</p>
<?php else : ?>
<p><?php esc_html_e( 'We\'ve noticed that your current password may have been compromised in a public leak. To keep your account safe, we\'ve added an extra layer of security.', 'jetpack-account-protection' ); ?></p>
<p>
<?php
Expand Down Expand Up @@ -241,12 +327,15 @@ class="action-input"
<button class="action action-verify" type="submit" name="verify"><?php esc_html_e( 'Verify', 'jetpack-account-protection' ); ?></button>
</form>
</div>
<p class="email-status">
<span><?php esc_html_e( 'Didn\'t get the code?', 'jetpack-account-protection' ); ?> </span>
<a href="<?php echo esc_url( $this->get_redirect_url( $token ) . '&resend_email=1&_wpnonce=' . wp_create_nonce( 'resend_email_nonce' ) ); ?>">
<?php esc_html_e( 'Resend email', 'jetpack-account-protection' ); ?>
</a>
</p>
<?php if ( $error_code !== 'email_resend_limit_error' ) : ?>
<p class="email-status">
<span><?php esc_html_e( "Didn't get the code?", 'jetpack-account-protection' ); ?> </span>
<a class="resend-email-link" href="<?php echo esc_url( $this->get_redirect_url( $token ) . '&resend_email=1&_wpnonce=' . wp_create_nonce( 'resend_email_nonce' ) ); ?>">
<?php esc_html_e( 'Resend email', 'jetpack-account-protection' ); ?>
</a>
</p>
<?php endif; ?>
<?php endif; ?>
</div>
<?php wp_footer(); ?>
</body>
Expand Down Expand Up @@ -290,7 +379,13 @@ private function generate_and_store_transient_data( int $user_id ): array {

$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' ) );
$this->set_transient_error(
$user_id,
array(
'code' => 'transient_error',
'message' => __( 'Failed to set transient data. Please try again.', 'jetpack-account-protection' ),
)
);
}

return array(
Expand Down Expand Up @@ -331,27 +426,51 @@ private function get_redirect_url( string $token ): string {
*/
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 ) {
$this->set_transient_success(
$user->ID,
array(
'code' => 'auth_code_success',
'message' => __( 'Authentication code verified successfully.', 'jetpack-account-protection' ),
)
);
// 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
$this->redirect_and_exit( admin_url() );
} else {
$this->set_transient_error( $user->ID, __( 'Authentication code verification failed. Please try again.', 'jetpack-account-protection' ) );
$this->set_transient_error(
$user->ID,
array(
'code' => 'auth_code_error',
'message' => __( 'Authentication code verification failed. Please try again.', 'jetpack-account-protection' ),
)
);
}
}

/**
* Set a transient success message.
*
* @param int $user_id The user ID.
* @param array $success An array of the success code and message.
* @param int $expiration The expiration time in seconds.
*
* @return void
*/
private function set_transient_success( int $user_id, array $success, int $expiration = 60 ): void {
set_transient( Config::TRANSIENT_PREFIX . "_success_{$user_id}", $success, $expiration );
}

/**
* Set a transient error message.
*
* @param int $user_id The user ID.
* @param string $message The error message.
* @param int $expiration The expiration time in seconds.
* @param int $user_id The user ID.
* @param array $error An array of the error code and message.
* @param int $expiration The expiration time in seconds.
*
* @return void
*/
private function set_transient_error( int $user_id, string $message, int $expiration = 60 ): void {
set_transient( Config::TRANSIENT_PREFIX . "_error_{$user_id}", $message, $expiration );
private function set_transient_error( int $user_id, array $error, int $expiration = 60 ): void {
set_transient( Config::TRANSIENT_PREFIX . "_error_{$user_id}", $error, $expiration );
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,15 @@
}

.action {
display: flex;
justify-content: center;
align-items: center;
height: 36px;
cursor: pointer;
width: 100%;
border-radius: 2px;
font-size: 13px;
text-decoration: none;
}

.action-input {
Expand All @@ -49,30 +54,52 @@
}
}

.action-verify {
.action-verify,
.action-update-password {
margin-top: 10px;
background-color: #0000EE;
border: 1px solid #0000EE;
color: #fff;
font-size: 13px;
border: 2px solid #0000EE;
color: #FFF;
}

.action-proceed {
background-color: #FFF;
border: 2px solid #0000EE;
color: #0000EE;
}

a.risks-link,
a.resend-email-link {
color: #0000EE;
}

.email-status {
text-align: center;
}

.notice-wrapper {
.notice {
width: 100%;
display: flex;
height: 36px;
align-items: center;
font-weight: 500;
box-sizing: border-box;
justify-content: center;
}

.notice-message {
text-align: center;
margin: 7px 0;
}

.error {
background: #FACFD2;
border: 2px solid #E65054;
color: #E65054;
}

.success {
background: #D0E6b8;
border: 2px solid #069E08;
color: #069E08;
}

Loading
Loading