From f9c073a582ec5726350944d465b73174dc6582cf Mon Sep 17 00:00:00 2001 From: Javier Casares Date: Thu, 17 Sep 2026 10:58:22 +0000 Subject: [PATCH 1/2] v1.6.6 --- changelog.txt | 20 ++++++++ includes/user/class-profile-settings.php | 60 ++++++++++++++++++++++++ readme.txt | 22 ++++----- robotstxt-2fa.php | 4 +- vendor/composer/installed.php | 4 +- 5 files changed, 93 insertions(+), 17 deletions(-) diff --git a/changelog.txt b/changelog.txt index 1e0e2bf..0c678d8 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,25 @@ == Changelog == += 1.6.6 = + +_Release date: 2026-09-17_ + +**Added** + +* Forced-enrollment warning on the profile 2FA section: when a user's role enforces 2FA but they have no verification method configured yet, the 2FA section of their profile (wp-admin and the frontend shortcode) opens with a warning notice asking them to activate at least one method. While a grace period is active, the notice appends the remaining days. + +**Compatibility** + +* WordPress: 5.6 – 7.1 +* PHP: 8.0 – 8.5 + +**Tests** + +* PHP Coding Standards: PHP_CodeSniffer 3.13.6 / WPCS 3.4.1 — 0 errors +* PHPStan: level 9 — 0 errors +* PHPCompatibility: 8.0–8.5 — 0 issues +* PHPUnit: 9.6.36 — 93 tests, 189 assertions + = 1.6.5 = _Release date: 2026-09-17_ diff --git a/includes/user/class-profile-settings.php b/includes/user/class-profile-settings.php index ff2ceb8..f136ca0 100644 --- a/includes/user/class-profile-settings.php +++ b/includes/user/class-profile-settings.php @@ -97,6 +97,13 @@ class Profile_Settings { */ private Trusted_Devices $trusted_devices; + /** + * Grace period manager. + * + * @var Grace_Period + */ + private Grace_Period $grace_period; + /** * Whether the post-save redirect should focus the 2FA section. * @@ -113,6 +120,7 @@ class Profile_Settings { $this->otp_manager = new OTP_Manager(); $this->recovery_codes = new Recovery_Codes(); $this->trusted_devices = new Trusted_Devices(); + $this->grace_period = new Grace_Period(); } /** @@ -170,6 +178,57 @@ class Profile_Settings { } + /** + * Render the forced-enrollment warning on the profile 2FA section. + * + * Shown when the user's role enforces 2FA but no verification method is + * configured yet, so the requirement is visible before the login flow + * (grace notice, setup wizard, or login block) kicks in. When a grace + * period is active, the number of remaining days is appended. + * + * @since 1.6.6 + * + * @param \WP_User $user User whose profile section is being rendered. + * + * @return void + */ + public function render_forced_enrollment_warning( \WP_User $user ): void { + $user_settings = $this->user_settings_repository->get_user_settings( $user->ID ); + + if ( empty( $this->config->get_required_methods_for_user( $user ) ) || ! empty( $user_settings['methods'] ) ) { + return; + } + + $grace_note = ''; + $grace_days = $this->config->get_grace_period_days(); + + if ( $grace_days > 0 && $this->grace_period->is_active( $user, $grace_days ) ) { + $remaining = $this->grace_period->get_days_remaining( $user, $grace_days ); + + /* translators: %d: number of days remaining to complete the setup. */ + $grace_note = sprintf( + _n( + 'You have %d day left to complete the setup.', + 'You have %d days left to complete the setup.', + $remaining, + 'robotstxt-2fa' + ), + $remaining + ); + } + ?> +
+

+ + + + + +

+
+

+ render_forced_enrollment_warning( $user ); ?> diff --git a/readme.txt b/readme.txt index 9526647..c42cca4 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Tags: security, two-factor authentication, login, otp Requires at least: 5.6 Tested up to: 7.0 Requires PHP: 8.0 -Stable tag: 1.6.5 +Stable tag: 1.6.6 License: GPLv3 or later License URI: https://www.gnu.org/licenses/gpl-3.0.html @@ -59,6 +59,14 @@ Yes. Activate the plugin at the network level. Network administrators can set an == Changelog == += 1.6.6 = + +_Release date: 2026-09-17_ + +**Added** + +* Forced-enrollment warning on the profile 2FA section: when a user's role enforces 2FA but they have no verification method configured yet, the 2FA section of their profile (wp-admin and the frontend shortcode) opens with a warning notice asking them to activate at least one method. While a grace period is active, the notice appends the remaining days. + = 1.6.5 = _Release date: 2026-09-17_ @@ -76,18 +84,6 @@ _Release date: 2026-09-11_ * Compatibility with Restrict Content Pro's "Hijack Login URL" option: RCP's `login_url` filter made every `wp_login_url()` call return a membership page, so the 2FA verification redirect landed on a restricted page where the verification form cannot render, and the visitor was bounced to the registration page. Verification-stage URLs are now built from the canonical `wp-login.php`, mirroring WordPress core's URL construction before the filterable output. The "Back to login" link keeps the site-configured login URL. -= 1.6.3 = - -_Release date: 2026-08-24_ - -**Changed** - -* Manager detection now uses the ecosystem presence constant (`ROBOTSTXT_MANAGER_NOTICED`, defined by Manager 1.6.2+) with a fallback to the plugin-list scan for older Manager versions. - -**Fixed** - -* Compatibility with the ALTCHA Spam Protection plugin: when "Protect login" was enabled, submitting the 2FA verification code failed with "[ALTCHA] Sorry, your request could not be processed.". The ALTCHA interceptor is now disabled while the verification screen is shown; the first login step keeps its ALTCHA check. - = Previous versions = For the full changelog see the [changelog](https://www.robotstxt.software/plugins/robotstxt-2fa/) page. diff --git a/robotstxt-2fa.php b/robotstxt-2fa.php index 3c1d9a4..8ff56f1 100644 --- a/robotstxt-2fa.php +++ b/robotstxt-2fa.php @@ -4,7 +4,7 @@ * Plugin URI: https://www.robotstxt.software/plugins/robotstxt-2fa/ * Update URI: https://www.robotstxt.software/plugins/robotstxt-2fa/ * Description: Adds two-factor authentication to the WordPress login flow. - * Version: 1.6.5 + * Version: 1.6.6 * Author: ROBOTSTXT * Author URI: https://www.robotstxt.software/ * Text Domain: robotstxt-2fa @@ -25,7 +25,7 @@ if ( ! defined( 'ABSPATH' ) ) { } if ( ! defined( 'ROBOTSTXT_2FA_VERSION' ) ) { - define( 'ROBOTSTXT_2FA_VERSION', '1.6.5' ); + define( 'ROBOTSTXT_2FA_VERSION', '1.6.6' ); } if ( ! defined( 'ROBOTSTXT_2FA_FILE' ) ) { diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php index 292a775..7328496 100644 --- a/vendor/composer/installed.php +++ b/vendor/composer/installed.php @@ -3,7 +3,7 @@ 'name' => 'robotstxt/robotstxt-2fa', 'pretty_version' => 'dev-main', 'version' => 'dev-main', - 'reference' => 'ed617610896f05f9dc90a3b0f53b19c9dacd59a5', + 'reference' => 'f1d493aefc9da797d99b3b635736b6c627166ddf', 'type' => 'wordpress-plugin', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -40,7 +40,7 @@ 'robotstxt/robotstxt-2fa' => array( 'pretty_version' => 'dev-main', 'version' => 'dev-main', - 'reference' => 'ed617610896f05f9dc90a3b0f53b19c9dacd59a5', + 'reference' => 'f1d493aefc9da797d99b3b635736b6c627166ddf', 'type' => 'wordpress-plugin', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), From c3f0eac50c4ac57a2e8c7c105c1dd6d32081c187 Mon Sep 17 00:00:00 2001 From: Javier Casares Date: Fri, 18 Sep 2026 04:36:07 +0000 Subject: [PATCH 2/2] v1.6.7 --- changelog.txt | 20 +++++++++++++ includes/user/class-grace-period.php | 38 +++++++++++++++++++++++- includes/user/class-profile-settings.php | 14 +++++++++ readme.txt | 18 +++++------ robotstxt-2fa.php | 4 +-- vendor/composer/installed.php | 4 +-- 6 files changed, 84 insertions(+), 14 deletions(-) diff --git a/changelog.txt b/changelog.txt index 0c678d8..67c38b2 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,25 @@ == Changelog == += 1.6.7 = + +_Release date: 2026-09-17_ + +**Fixed** + +* The forced-setup wizard felt like a broken redirect loop: the wizard redirect bounced users to the profile with no explanation (it now carries a flag that renders a clear "activate at least one verification method, then save" warning), and saving with an expired form nonce silently did nothing (it now raises a visible "form session expired" error so users know to try again). + +**Compatibility** + +* WordPress: 5.6 – 7.1 +* PHP: 8.0 – 8.5 + +**Tests** + +* PHP Coding Standards: PHP_CodeSniffer 3.13.6 / WPCS 3.4.1 — 0 errors +* PHPStan: level 9 — 0 errors +* PHPCompatibility: 8.0–8.5 — 0 issues +* PHPUnit: 9.6.36 — 98 tests, 197 assertions + = 1.6.6 = _Release date: 2026-09-17_ diff --git a/includes/user/class-grace-period.php b/includes/user/class-grace-period.php index b074a79..31399d6 100644 --- a/includes/user/class-grace-period.php +++ b/includes/user/class-grace-period.php @@ -41,6 +41,7 @@ class Grace_Period { public function register_hooks(): void { add_action( 'admin_init', array( $this, 'maybe_redirect_to_setup_wizard' ) ); add_action( 'robotstxt_2fa_method_enabled', array( $this, 'clear_pending_setup' ), 10, 2 ); + add_action( 'admin_notices', array( $this, 'maybe_show_setup_required_notice' ) ); } /** @@ -167,10 +168,45 @@ class Grace_Period { return; } - wp_safe_redirect( admin_url( 'profile.php#robotstxt-2fa-settings' ) ); + // The query flag lets the profile show WHY the user was redirected, + // so the mandatory setup does not feel like a silent loop. + $profile_url = add_query_arg( 'robotstxt-2fa-setup-required', '1', admin_url( 'profile.php' ) ); + + wp_safe_redirect( $profile_url . '#robotstxt-2fa-settings' ); exit; } + /** + * Explain the setup wizard redirect on the profile screen. + * + * Rendered after {@see self::maybe_redirect_to_setup_wizard()} bounced the + * user back to their profile. Without it, users land on a regular profile + * page with no hint that 2FA enrollment is mandatory before they can + * continue to the dashboard — which reads as a broken redirect loop. + * + * @since 1.6.7 + * + * @return void + */ + public function maybe_show_setup_required_notice(): void { + if ( ! $this->is_pending_setup() ) { + return; + } + + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display-only flag; no state change. + if ( ! isset( $_GET['robotstxt-2fa-setup-required'] ) ) { + return; + } + ?> +
+

+ + +

+
+ focus_section = true; + add_settings_error( + 'robotstxt-2fa', + 'robotstxt-2fa-nonce-expired', + __( 'Your form session expired and nothing was saved. Please try saving again.', 'robotstxt-2fa' ), + 'error' + ); + return; } diff --git a/readme.txt b/readme.txt index c42cca4..fd801dc 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Tags: security, two-factor authentication, login, otp Requires at least: 5.6 Tested up to: 7.0 Requires PHP: 8.0 -Stable tag: 1.6.6 +Stable tag: 1.6.7 License: GPLv3 or later License URI: https://www.gnu.org/licenses/gpl-3.0.html @@ -59,6 +59,14 @@ Yes. Activate the plugin at the network level. Network administrators can set an == Changelog == += 1.6.7 = + +_Release date: 2026-09-17_ + +**Fixed** + +* The forced-setup wizard felt like a broken redirect loop: the wizard redirect bounced users to the profile with no explanation (it now carries a flag that renders a clear "activate at least one verification method, then save" warning), and saving with an expired form nonce silently did nothing (it now raises a visible "form session expired" error so users know to try again). + = 1.6.6 = _Release date: 2026-09-17_ @@ -76,14 +84,6 @@ _Release date: 2026-09-17_ * Fatal error on the login screen with newer WordPress versions: the `login_message` filter can deliver null when no message is set (observed on WordPress 7.x with PHP 8.4, TypeError on a plain visit to wp-login.php). The message callback now accepts `string|null` and coalesces null to an empty string. * Hardened all externally-fed filter callbacks against null payloads: the four `authenticate` callbacks now accept `string|null` credentials (custom REST/SSO endpoints are known to apply the filter with null), and the `wp_redirect` filter callback coalesces a null location. -= 1.6.4 = - -_Release date: 2026-09-11_ - -**Fixed** - -* Compatibility with Restrict Content Pro's "Hijack Login URL" option: RCP's `login_url` filter made every `wp_login_url()` call return a membership page, so the 2FA verification redirect landed on a restricted page where the verification form cannot render, and the visitor was bounced to the registration page. Verification-stage URLs are now built from the canonical `wp-login.php`, mirroring WordPress core's URL construction before the filterable output. The "Back to login" link keeps the site-configured login URL. - = Previous versions = For the full changelog see the [changelog](https://www.robotstxt.software/plugins/robotstxt-2fa/) page. diff --git a/robotstxt-2fa.php b/robotstxt-2fa.php index 8ff56f1..1fb6b43 100644 --- a/robotstxt-2fa.php +++ b/robotstxt-2fa.php @@ -4,7 +4,7 @@ * Plugin URI: https://www.robotstxt.software/plugins/robotstxt-2fa/ * Update URI: https://www.robotstxt.software/plugins/robotstxt-2fa/ * Description: Adds two-factor authentication to the WordPress login flow. - * Version: 1.6.6 + * Version: 1.6.7 * Author: ROBOTSTXT * Author URI: https://www.robotstxt.software/ * Text Domain: robotstxt-2fa @@ -25,7 +25,7 @@ if ( ! defined( 'ABSPATH' ) ) { } if ( ! defined( 'ROBOTSTXT_2FA_VERSION' ) ) { - define( 'ROBOTSTXT_2FA_VERSION', '1.6.6' ); + define( 'ROBOTSTXT_2FA_VERSION', '1.6.7' ); } if ( ! defined( 'ROBOTSTXT_2FA_FILE' ) ) { diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php index 7328496..3b5c785 100644 --- a/vendor/composer/installed.php +++ b/vendor/composer/installed.php @@ -3,7 +3,7 @@ 'name' => 'robotstxt/robotstxt-2fa', 'pretty_version' => 'dev-main', 'version' => 'dev-main', - 'reference' => 'f1d493aefc9da797d99b3b635736b6c627166ddf', + 'reference' => '2b6e97d03ffbc5f7734bbd30413c979f50de3d15', 'type' => 'wordpress-plugin', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -40,7 +40,7 @@ 'robotstxt/robotstxt-2fa' => array( 'pretty_version' => 'dev-main', 'version' => 'dev-main', - 'reference' => 'f1d493aefc9da797d99b3b635736b6c627166ddf', + 'reference' => '2b6e97d03ffbc5f7734bbd30413c979f50de3d15', 'type' => 'wordpress-plugin', 'install_path' => __DIR__ . '/../../', 'aliases' => array(),