From c778c217822d090d638c9450c502328b1d6a35e3 Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Fri, 16 Aug 2024 02:58:53 +0000 Subject: [PATCH 01/11] Add initial interstitial for 'You have no recovery codes' --- common/includes/wporg-sso/wp-plugin.php | 72 ++++++++++++++- .../themes/pub/wporg-login/recovery-codes.php | 88 +++++++++++++++++++ 2 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 wordpress.org/public_html/wp-content/themes/pub/wporg-login/recovery-codes.php diff --git a/common/includes/wporg-sso/wp-plugin.php b/common/includes/wporg-sso/wp-plugin.php index 178311f285..96dab36d0b 100644 --- a/common/includes/wporg-sso/wp-plugin.php +++ b/common/includes/wporg-sso/wp-plugin.php @@ -29,6 +29,7 @@ class WP_WPOrg_SSO extends WPOrg_SSO { // Primarily for logged in users. 'updated-tos' => '/updated-policies', 'enable-2fa' => '/enable-2fa', + 'recovery-codes' => '/recovery-codes', 'logout' => '/logout', // Primarily for logged out users. @@ -55,6 +56,13 @@ class WP_WPOrg_SSO extends WPOrg_SSO { */ static $matched_route_params = array(); + /** + * Holds the last set auth cookie. + * + * @var array + */ + protected $last_auth_cookie = array(); + /** * Constructor: add our action(s)/filter(s) */ @@ -98,12 +106,25 @@ public function __construct() { // Updated TOS interceptor. add_filter( 'send_auth_cookies', [ $this, 'maybe_block_auth_cookies' ], 100, 5 ); + // See https://core.trac.wordpress.org/ticket/61874 + add_action( 'set_auth_cookie', [ $this, 'record_last_auth_cookie' ], 10, 6 ); + // Maybe nag about 2FA - add_filter( 'login_redirect', [ $this, 'maybe_redirect_to_enable_2fa' ], 1000, 3 ); + add_filter( 'login_redirect', [ $this, 'maybe_redirect_to_recovery_codes' ], 500, 3 ); + add_filter( 'login_redirect', [ $this, 'maybe_redirect_to_enable_2fa' ], 1100, 3 ); } } } + /** + * Records the last set cookies, because WordPress. + * + * @see https://core.trac.wordpress.org/ticket/61874 + */ + function record_last_auth_cookie( $auth_cookie, $expire, $expiration, $user_id, $scheme, $token ) { + $this->last_auth_cookie = compact( 'auth_cookie', 'expire', 'expiration', 'user_id', 'scheme', 'token' ); + } + /** * Inherits the 'registration' option from the main network. * @@ -851,6 +872,55 @@ public function maybe_redirect_to_enable_2fa( $redirect, $orig_redirect, $user ) ); } + /** + * Redirects the user to the 2FA Recovery codes nag if needed. + */ + public function maybe_redirect_to_recovery_codes( $redirect, $orig_redirect, $user ) { + if ( + // No valid user. + is_wp_error( $user ) || + // Or we're already going there. + str_contains( $redirect, '/recovery-codes' ) || + // Or the user doesn't use 2FA + ! Two_Factor_Core::is_user_using_two_factor( $user->ID ) + ) { + // Then we don't need to redirect to the enable 2FA page. + return $redirect; + } + + $current_user = wp_get_current_user(); + + // If the user logged in with a recovery code.. + $session_token = wp_get_session_token() ?: ( $this->last_auth_cookie['token'] ?? '' ); + $session = WP_Session_Tokens::get_instance( $user->ID )->get( $session_token ); + $used_recovery_code = str_contains( $session['two-factor-provider'] ?? '', 'Backup_Codes' ); + $codes_available = Two_Factor_Backup_Codes::codes_remaining_for_user( $user ); + + if ( + // If they didn't use a recovery code, + ! $used_recovery_code && + ( + // They have ample codes available.. + $codes_available > 3 || + // or they've already been nagged about only having a few left (and actually have them) + ( + $codes_available && + $codes_available >= (int) get_user_meta( $user->ID, 'last_2fa_recovery_codes_nag', true ) + ) + ) + ) { + // No need to nag. + return $redirect; + } + + // Redirect to the Recovery Codes nag. + return add_query_arg( + 'redirect_to', + urlencode( $redirect ), + home_url( '/recovery-codes' ) + ); + } + /** * Whether the given user_id has agreed to the current version of the TOS. */ diff --git a/wordpress.org/public_html/wp-content/themes/pub/wporg-login/recovery-codes.php b/wordpress.org/public_html/wp-content/themes/pub/wporg-login/recovery-codes.php new file mode 100644 index 0000000000..c6f37eb79c --- /dev/null +++ b/wordpress.org/public_html/wp-content/themes/pub/wporg-login/recovery-codes.php @@ -0,0 +1,88 @@ +ID )->get( wp_get_session_token() ); +$used_recovery_code = str_contains( $session['two-factor-provider'] ?? '', 'Backup_Codes' ); +$codes_available = Two_Factor_Backup_Codes::codes_remaining_for_user( $user ); +$can_ignore = ! $used_recovery_code || ( $used_recovery_code && $codes_available > 1 ); + +if ( isset( $_REQUEST['redirect_to'] ) ) { + $redirect_to = wp_validate_redirect( wp_unslash( $_REQUEST['redirect_to'] ), $redirect_to ); +} + +// If the user is here in error, redirect off. +if ( ! is_user_logged_in() || ! Two_Factor_Core::is_user_using_two_factor( $user->ID ) ) { + wp_safe_redirect( $redirect_to ); + exit; +} + +/** + * Record the last time we nagged the user about recovery codes, as we only want to do this once per code-use. + */ +update_user_meta( $user->ID, 'last_2fa_recovery_codes_nag', $codes_available ); + +get_header(); +?> + +

+ +

 

+ +

These codes are intended to be used when you lose access to your authentication device.
Please take a moment to review your Account Settings and ensure your Two-Factor settings are updated.", 'wporg-login' ); + } else { + if ( ! $codes_available ) { + _e( "Warning! You don't have any Recovery Codes left.", 'wporg-login' ); + } else { + printf( + _n( + "Warning! You've only got %s Recovery Code left.", + "Warning! You've only got %s Recovery Codes left.", + $codes_available, + 'wporg-login' + ), + '' . number_format_i18n( $codes_available ) . '' + ); + } + + // Direct to the backup codes screen. + $account_settings_url = add_query_arg( 'screen', 'backup-codes', $account_settings_url ); + } +?>

+ +

 

+ +

+ +

 

+ +

+ + + + + + From 7c3288174050ffbc82d2854ac443250c4ed6675f Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Fri, 16 Aug 2024 02:59:15 +0000 Subject: [PATCH 02/11] Sync the logged out experience for the enable-2fa page with the recovery-codes page. --- .../wp-content/themes/pub/wporg-login/enable-2fa.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/wordpress.org/public_html/wp-content/themes/pub/wporg-login/enable-2fa.php b/wordpress.org/public_html/wp-content/themes/pub/wporg-login/enable-2fa.php index f0db46d09b..17d5ababbf 100644 --- a/wordpress.org/public_html/wp-content/themes/pub/wporg-login/enable-2fa.php +++ b/wordpress.org/public_html/wp-content/themes/pub/wporg-login/enable-2fa.php @@ -9,7 +9,16 @@ $user = wp_get_current_user(); $requires_2fa = user_requires_2fa( $user ); $should_2fa = user_should_2fa( $user ); // If they're on this page, this should be truthful. -$redirect_to = wp_validate_redirect( wp_unslash( $_REQUEST['redirect_to'] ?? '' ), wporg_login_wordpress_url() ); +$redirect_to = wporg_login_wordpress_url(); +if ( isset( $_REQUEST['redirect_to'] ) ) { + $redirect_to = wp_validate_redirect( wp_unslash( $_REQUEST['redirect_to'] ), $redirect_to ); +} + +// If the user is here in error, redirect off. +if ( ! is_user_logged_in() || Two_Factor_Core::is_user_using_two_factor( $user->ID ) ) { + wp_safe_redirect( $redirect_to ); + exit; +} /* * Record the last time we naged the user about 2FA. From 07c9b5f252adf56aec7437d5e40c038f60d1c635 Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Fri, 16 Aug 2024 03:08:23 +0000 Subject: [PATCH 03/11] Remove unused variable. --- common/includes/wporg-sso/wp-plugin.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/common/includes/wporg-sso/wp-plugin.php b/common/includes/wporg-sso/wp-plugin.php index 96dab36d0b..26ac235ca1 100644 --- a/common/includes/wporg-sso/wp-plugin.php +++ b/common/includes/wporg-sso/wp-plugin.php @@ -888,8 +888,6 @@ public function maybe_redirect_to_recovery_codes( $redirect, $orig_redirect, $us return $redirect; } - $current_user = wp_get_current_user(); - // If the user logged in with a recovery code.. $session_token = wp_get_session_token() ?: ( $this->last_auth_cookie['token'] ?? '' ); $session = WP_Session_Tokens::get_instance( $user->ID )->get( $session_token ); From 2ffd4da183a42eeece712a6a2871e3b8262e3a8c Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Fri, 16 Aug 2024 16:32:40 +1000 Subject: [PATCH 04/11] Lowercase account settings Co-authored-by: Steven Dufresne --- .../wp-content/themes/pub/wporg-login/recovery-codes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wordpress.org/public_html/wp-content/themes/pub/wporg-login/recovery-codes.php b/wordpress.org/public_html/wp-content/themes/pub/wporg-login/recovery-codes.php index c6f37eb79c..6a9c386842 100644 --- a/wordpress.org/public_html/wp-content/themes/pub/wporg-login/recovery-codes.php +++ b/wordpress.org/public_html/wp-content/themes/pub/wporg-login/recovery-codes.php @@ -48,7 +48,7 @@

These codes are intended to be used when you lose access to your authentication device.
Please take a moment to review your Account Settings and ensure your Two-Factor settings are updated.", 'wporg-login' ); + _e( "You've logged in with a Recovery Code.
These codes are intended to be used when you lose access to your authentication device.
Please take a moment to review your account settings and ensure your Two-Factor settings are up-to-date.", 'wporg-login' ); } else { if ( ! $codes_available ) { _e( "Warning! You don't have any Recovery Codes left.", 'wporg-login' ); From bb4ad584eaeaecfad748fcbca34a5d16b8ce0fdb Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Fri, 16 Aug 2024 16:35:17 +1000 Subject: [PATCH 05/11] Add more context to the reason we store the auth cookie details. --- common/includes/wporg-sso/wp-plugin.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/common/includes/wporg-sso/wp-plugin.php b/common/includes/wporg-sso/wp-plugin.php index 26ac235ca1..0052d35532 100644 --- a/common/includes/wporg-sso/wp-plugin.php +++ b/common/includes/wporg-sso/wp-plugin.php @@ -119,6 +119,10 @@ public function __construct() { /** * Records the last set cookies, because WordPress. * + * During the WordPress login process, the authentication cookies are not yet available, + * but we need to know the user token (contained in those cookies) to retrieve their session. + * To work around this, we store the set authentication cookies here for later usage. + * * @see https://core.trac.wordpress.org/ticket/61874 */ function record_last_auth_cookie( $auth_cookie, $expire, $expiration, $user_id, $scheme, $token ) { From 64aaf5e9781ea38a7f0d3a17c68aef259fedfe5c Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Fri, 16 Aug 2024 17:36:46 +1000 Subject: [PATCH 06/11] Sentence case. Co-authored-by: Steven Dufresne --- .../wp-content/themes/pub/wporg-login/recovery-codes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wordpress.org/public_html/wp-content/themes/pub/wporg-login/recovery-codes.php b/wordpress.org/public_html/wp-content/themes/pub/wporg-login/recovery-codes.php index 6a9c386842..ca6a75e34e 100644 --- a/wordpress.org/public_html/wp-content/themes/pub/wporg-login/recovery-codes.php +++ b/wordpress.org/public_html/wp-content/themes/pub/wporg-login/recovery-codes.php @@ -77,7 +77,7 @@

 

-

+

 

These codes are intended to be used when you lose access to your authentication device.
Please take a moment to review your account settings and ensure your Two-Factor settings are up-to-date.", 'wporg-login' ); + if ( $used_backup_code ) { + _e( "You've logged in with a Backup Code.
These codes are intended to be used when you lose access to your authentication device.
Please take a moment to review your account settings and ensure your Two-Factor settings are up-to-date.", 'wporg-login' ); } else { if ( ! $codes_available ) { - _e( "Warning! You don't have any Recovery Codes left.", 'wporg-login' ); + _e( "Warning! You don't have any Backup Codes left.", 'wporg-login' ); } else { printf( _n( - "Warning! You've only got %s Recovery Code left.", - "Warning! You've only got %s Recovery Codes left.", + "Warning! You've only got %s Backup Code left.", + "Warning! You've only got %s Backup Codes left.", $codes_available, 'wporg-login' ), @@ -72,7 +72,7 @@

 

 

From 83ed850d34e638967a69876702afb2e71b5f81e7 Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Tue, 20 Aug 2024 12:12:39 +1000 Subject: [PATCH 10/11] s/recovery/backup/g --- .../wp-content/themes/pub/wporg-login/backup-codes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wordpress.org/public_html/wp-content/themes/pub/wporg-login/backup-codes.php b/wordpress.org/public_html/wp-content/themes/pub/wporg-login/backup-codes.php index 2d50c83134..06b3a8f844 100644 --- a/wordpress.org/public_html/wp-content/themes/pub/wporg-login/backup-codes.php +++ b/wordpress.org/public_html/wp-content/themes/pub/wporg-login/backup-codes.php @@ -37,7 +37,7 @@ ?>

Date: Tue, 20 Aug 2024 12:25:40 +1000 Subject: [PATCH 11/11] Sentence case, better phrasing. --- .../wp-content/themes/pub/wporg-login/backup-codes.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/wordpress.org/public_html/wp-content/themes/pub/wporg-login/backup-codes.php b/wordpress.org/public_html/wp-content/themes/pub/wporg-login/backup-codes.php index 06b3a8f844..949391b147 100644 --- a/wordpress.org/public_html/wp-content/themes/pub/wporg-login/backup-codes.php +++ b/wordpress.org/public_html/wp-content/themes/pub/wporg-login/backup-codes.php @@ -48,15 +48,15 @@

These codes are intended to be used when you lose access to your authentication device.
Please take a moment to review your account settings and ensure your Two-Factor settings are up-to-date.", 'wporg-login' ); + _e( "You've logged in with a backup code.
These codes are intended to be used when you lose access to your authentication device.
Please take a moment to review your account settings and ensure your two-factor settings are up-to-date.", 'wporg-login' ); } else { if ( ! $codes_available ) { - _e( "Warning! You don't have any Backup Codes left.", 'wporg-login' ); + _e( 'You do not have any backup codes remaining.', 'wporg-login' ); } else { printf( _n( - "Warning! You've only got %s Backup Code left.", - "Warning! You've only got %s Backup Codes left.", + 'You have %s backup code remaining.', + 'You have %s backup codes remaining.', $codes_available, 'wporg-login' ), @@ -72,7 +72,7 @@