Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c3f0eac50c |
6 changed files with 84 additions and 14 deletions
|
|
@ -1,5 +1,25 @@
|
||||||
== Changelog ==
|
== 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 =
|
= 1.6.6 =
|
||||||
|
|
||||||
_Release date: 2026-09-17_
|
_Release date: 2026-09-17_
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ class Grace_Period {
|
||||||
public function register_hooks(): void {
|
public function register_hooks(): void {
|
||||||
add_action( 'admin_init', array( $this, 'maybe_redirect_to_setup_wizard' ) );
|
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( '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;
|
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;
|
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;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<div class="notice notice-warning robotstxt-2fa-setup-required">
|
||||||
|
<p>
|
||||||
|
<strong><?php esc_html_e( 'Two-factor authentication is required.', 'robotstxt-2fa' ); ?></strong>
|
||||||
|
<?php esc_html_e( 'You must activate at least one verification method in the Two-Factor Authentication section of this page, then save, before you can continue to the dashboard.', 'robotstxt-2fa' ); ?>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the grace period start timestamp, creating it on first call.
|
* Retrieve the grace period start timestamp, creating it on first call.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -858,6 +858,20 @@ class Profile_Settings {
|
||||||
$nonce = sanitize_text_field( wp_unslash( $_POST[ self::NONCE_FIELD ] ) );
|
$nonce = sanitize_text_field( wp_unslash( $_POST[ self::NONCE_FIELD ] ) );
|
||||||
|
|
||||||
if ( ! wp_verify_nonce( $nonce, self::NONCE_ACTION ) ) {
|
if ( ! wp_verify_nonce( $nonce, self::NONCE_ACTION ) ) {
|
||||||
|
/*
|
||||||
|
* A stale form (nonce expired) previously returned silently, which
|
||||||
|
* left wizard-trapped users clicking Save with no visible result
|
||||||
|
* while the setup-required redirect kept bouncing them back to the
|
||||||
|
* profile. Fail loudly so the user knows to try again.
|
||||||
|
*/
|
||||||
|
$this->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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
18
readme.txt
18
readme.txt
|
|
@ -4,7 +4,7 @@ Tags: security, two-factor authentication, login, otp
|
||||||
Requires at least: 5.6
|
Requires at least: 5.6
|
||||||
Tested up to: 7.0
|
Tested up to: 7.0
|
||||||
Requires PHP: 8.0
|
Requires PHP: 8.0
|
||||||
Stable tag: 1.6.6
|
Stable tag: 1.6.7
|
||||||
License: GPLv3 or later
|
License: GPLv3 or later
|
||||||
License URI: https://www.gnu.org/licenses/gpl-3.0.html
|
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 ==
|
== 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 =
|
= 1.6.6 =
|
||||||
|
|
||||||
_Release date: 2026-09-17_
|
_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.
|
* 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.
|
* 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 =
|
= Previous versions =
|
||||||
|
|
||||||
For the full changelog see the [changelog](https://www.robotstxt.software/plugins/robotstxt-2fa/) page.
|
For the full changelog see the [changelog](https://www.robotstxt.software/plugins/robotstxt-2fa/) page.
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
* Plugin URI: https://www.robotstxt.software/plugins/robotstxt-2fa/
|
* Plugin URI: https://www.robotstxt.software/plugins/robotstxt-2fa/
|
||||||
* Update 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.
|
* Description: Adds two-factor authentication to the WordPress login flow.
|
||||||
* Version: 1.6.6
|
* Version: 1.6.7
|
||||||
* Author: ROBOTSTXT
|
* Author: ROBOTSTXT
|
||||||
* Author URI: https://www.robotstxt.software/
|
* Author URI: https://www.robotstxt.software/
|
||||||
* Text Domain: robotstxt-2fa
|
* Text Domain: robotstxt-2fa
|
||||||
|
|
@ -25,7 +25,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ! defined( 'ROBOTSTXT_2FA_VERSION' ) ) {
|
if ( ! defined( 'ROBOTSTXT_2FA_VERSION' ) ) {
|
||||||
define( 'ROBOTSTXT_2FA_VERSION', '1.6.6' );
|
define( 'ROBOTSTXT_2FA_VERSION', '1.6.7' );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ! defined( 'ROBOTSTXT_2FA_FILE' ) ) {
|
if ( ! defined( 'ROBOTSTXT_2FA_FILE' ) ) {
|
||||||
|
|
|
||||||
4
vendor/composer/installed.php
vendored
4
vendor/composer/installed.php
vendored
|
|
@ -3,7 +3,7 @@
|
||||||
'name' => 'robotstxt/robotstxt-2fa',
|
'name' => 'robotstxt/robotstxt-2fa',
|
||||||
'pretty_version' => 'dev-main',
|
'pretty_version' => 'dev-main',
|
||||||
'version' => 'dev-main',
|
'version' => 'dev-main',
|
||||||
'reference' => 'f1d493aefc9da797d99b3b635736b6c627166ddf',
|
'reference' => '2b6e97d03ffbc5f7734bbd30413c979f50de3d15',
|
||||||
'type' => 'wordpress-plugin',
|
'type' => 'wordpress-plugin',
|
||||||
'install_path' => __DIR__ . '/../../',
|
'install_path' => __DIR__ . '/../../',
|
||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
|
|
@ -40,7 +40,7 @@
|
||||||
'robotstxt/robotstxt-2fa' => array(
|
'robotstxt/robotstxt-2fa' => array(
|
||||||
'pretty_version' => 'dev-main',
|
'pretty_version' => 'dev-main',
|
||||||
'version' => 'dev-main',
|
'version' => 'dev-main',
|
||||||
'reference' => 'f1d493aefc9da797d99b3b635736b6c627166ddf',
|
'reference' => '2b6e97d03ffbc5f7734bbd30413c979f50de3d15',
|
||||||
'type' => 'wordpress-plugin',
|
'type' => 'wordpress-plugin',
|
||||||
'install_path' => __DIR__ . '/../../',
|
'install_path' => __DIR__ . '/../../',
|
||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue