Skip to content

Commit

Permalink
Account Protection: Improve auth success flow (#41727)
Browse files Browse the repository at this point in the history
  • Loading branch information
dkmyta authored and nateweller committed Feb 20, 2025
1 parent 775608f commit 05d389c
Show file tree
Hide file tree
Showing 3 changed files with 218 additions and 50 deletions.
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 @@ -60,7 +60,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 @@ -152,21 +158,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::PASSWORD_DETECTION_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 @@ -177,7 +203,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 @@ -193,9 +225,36 @@ public function render_page() {
* @return void
*/
public function render_content( \WP_User $user, string $token ): void {
$transient_key = Config::PASSWORD_DETECTION_TRANSIENT_PREFIX . "_error_{$user->ID}";
$error_message = get_transient( $transient_key );
delete_transient( $transient_key );
$error_transient_key = Config::PASSWORD_DETECTION_TRANSIENT_PREFIX . "_error_{$user->ID}";
$success_transient_key = Config::PASSWORD_DETECTION_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 @@ -210,12 +269,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 @@ -244,12 +330,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 @@ -293,7 +382,13 @@ private function generate_and_store_transient_data( int $user_id ): array {

$transient_set = set_transient( Config::PASSWORD_DETECTION_TRANSIENT_PREFIX . "_{$token}", $data, Config::PASSWORD_DETECTION_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 @@ -334,27 +429,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::PASSWORD_DETECTION_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::PASSWORD_DETECTION_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::PASSWORD_DETECTION_TRANSIENT_PREFIX . "_error_{$user_id}", $message, $expiration );
private function set_transient_error( int $user_id, array $error, int $expiration = 60 ): void {
set_transient( Config::PASSWORD_DETECTION_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

0 comments on commit 05d389c

Please sign in to comment.