Skip to content

Commit e91ef54

Browse files
mikelittleclaude
authored andcommitted
Remove plaintext passwords from multisite activation flow
After account activation, redirect users to the password reset page instead of displaying the plaintext password on wp-activate.php. Replace plaintext passwords in welcome emails with a message directing users to set their password via the login page. - humanmade/product-dev#2009 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> (cherry picked from commit 264f061)
1 parent b219daa commit e91ef54

File tree

1 file changed

+88
-2
lines changed

1 file changed

+88
-2
lines changed

inc/signup_notification/namespace.php

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ function bootstrap() {
2020

2121
add_action( 'after_signup_user', __NAMESPACE__ . '\\altis_signup_user_notification', 10, 4 );
2222
add_action( 'wpmu_activate_user', __NAMESPACE__ . '\\altis_welcome_user_notification', 10, 3 );
23+
24+
// Redirect to password reset after activation (priority 20, after welcome emails at 10).
25+
add_action( 'wpmu_activate_user', __NAMESPACE__ . '\\redirect_to_password_reset', 20, 3 );
26+
add_action( 'wpmu_activate_blog', __NAMESPACE__ . '\\redirect_blog_user_to_password_reset', 20, 5 );
2327
}
2428

2529
/**
@@ -374,7 +378,7 @@ function altis_welcome_notification( $blog_id, $user_id, $password, $title, $met
374378
$welcome_email = str_replace( 'BLOG_TITLE', $title, $welcome_email );
375379
$welcome_email = str_replace( 'BLOG_URL', $url, $welcome_email );
376380
$welcome_email = str_replace( 'USERNAME', $user->user_login, $welcome_email );
377-
$welcome_email = str_replace( 'PASSWORD', $password, $welcome_email );
381+
$welcome_email = str_replace( 'PASSWORD', __( 'Please set your password by visiting the login page.' ), $welcome_email );
378382

379383
/**
380384
* Filters the content of the welcome email sent to the site administrator after site activation.
@@ -483,7 +487,7 @@ function altis_welcome_user_notification( $user_id, $password, $meta = [] ) {
483487
$welcome_email = apply_filters( 'update_welcome_user_email', $welcome_email, $user_id, $password, $meta );
484488
$welcome_email = str_replace( 'SITE_NAME', $current_network->site_name, $welcome_email );
485489
$welcome_email = str_replace( 'USERNAME', $user->user_login, $welcome_email );
486-
$welcome_email = str_replace( 'PASSWORD', $password, $welcome_email );
490+
$welcome_email = str_replace( 'PASSWORD', __( 'Please set your password by visiting the login page.' ), $welcome_email );
487491
$welcome_email = str_replace( 'LOGINLINK', wp_login_url(), $welcome_email );
488492

489493
$admin_email = get_site_option( 'admin_email' );
@@ -522,3 +526,85 @@ function altis_welcome_user_notification( $user_id, $password, $meta = [] ) {
522526

523527
return true;
524528
}
529+
530+
/**
531+
* Redirect a user to the password reset page after account activation.
532+
*
533+
* Fires on `wpmu_activate_user` at priority 20, after the welcome email
534+
* has been sent at priority 10. This prevents the plaintext password from
535+
* being displayed on the wp-activate.php page.
536+
*
537+
* @param int $user_id User ID.
538+
* @param string $password User password.
539+
* @param array $meta Signup meta data.
540+
*/
541+
function redirect_to_password_reset( $user_id, $password, $meta ) {
542+
$user = get_userdata( $user_id );
543+
if ( ! $user ) {
544+
return;
545+
}
546+
547+
$reset_key = get_password_reset_key( $user );
548+
if ( is_wp_error( $reset_key ) ) {
549+
return;
550+
}
551+
552+
$login_url = network_site_url( 'wp-login.php', 'login' );
553+
$redirect_url = add_query_arg( [
554+
'action' => 'rp',
555+
'key' => $reset_key,
556+
'login' => rawurlencode( $user->user_login ),
557+
], $login_url );
558+
559+
// Invalidate the wp-user-signups cache so the admin list table reflects
560+
// the updated activation status. Core's wpmu_activate_signup() updates
561+
// the DB directly but doesn't clean the plugin's object cache.
562+
wp_cache_set( 'last_changed', microtime(), 'signups' );
563+
564+
wp_safe_redirect( $redirect_url );
565+
exit;
566+
}
567+
568+
/**
569+
* Redirect a blog signup user to the password reset page after activation.
570+
*
571+
* Fires on `wpmu_activate_blog` at priority 20, after the welcome email
572+
* has been sent at priority 10. This prevents the plaintext password from
573+
* being displayed on the wp-activate.php page.
574+
*
575+
* @param int $blog_id Blog ID.
576+
* @param int $user_id User ID.
577+
* @param string $password User password.
578+
* @param string $title Site title.
579+
* @param array $meta Signup meta data.
580+
*/
581+
function redirect_blog_user_to_password_reset( $blog_id, $user_id, $password, $title, $meta ) {
582+
$user = get_userdata( $user_id );
583+
if ( ! $user ) {
584+
return;
585+
}
586+
587+
$reset_key = get_password_reset_key( $user );
588+
if ( is_wp_error( $reset_key ) ) {
589+
return;
590+
}
591+
592+
// Use the network login URL rather than the new blog's login URL.
593+
// The password reset key works network-wide, and wp_safe_redirect()
594+
// may reject cross-host redirects to the new blog's subdomain.
595+
$login_url = network_site_url( 'wp-login.php', 'login' );
596+
597+
$redirect_url = add_query_arg( [
598+
'action' => 'rp',
599+
'key' => $reset_key,
600+
'login' => rawurlencode( $user->user_login ),
601+
], $login_url );
602+
603+
// Invalidate the wp-user-signups cache so the admin list table reflects
604+
// the updated activation status. Core's wpmu_activate_signup() updates
605+
// the DB directly but doesn't clean the plugin's object cache.
606+
wp_cache_set( 'last_changed', microtime(), 'signups' );
607+
608+
wp_safe_redirect( $redirect_url );
609+
exit;
610+
}

0 commit comments

Comments
 (0)