diff --git a/class-two-factor-core.php b/class-two-factor-core.php
index 7c0ad9a0..76a6f5a7 100644
--- a/class-two-factor-core.php
+++ b/class-two-factor-core.php
@@ -1022,10 +1022,10 @@ public static function maybe_show_last_login_failure_notice( $user ) {
echo '
';
printf(
esc_html(
- /* translators: 1: number of failed login attempts, 2: time since last failed attempt */
+ /* translators: 1: number of failed verification code attempts, 2: human-readable time since the last attempt, e.g. "5 minutes" */
_n(
- 'WARNING: Your account has attempted to login %1$s time without providing a valid two factor token. The last failed login occurred %2$s ago. If this wasn\'t you, you should reset your password.',
- 'WARNING: Your account has attempted to login %1$s times without providing a valid two factor token. The last failed login occurred %2$s ago. If this wasn\'t you, you should reset your password.',
+ '%1$s failed verification code attempt on this account. The last attempt was %2$s ago. If you did not make this attempt, someone else may know your password. Change your password after you log in.',
+ '%1$s failed verification code attempts on this account. The last attempt was %2$s ago. If you did not make these attempts, someone else may know your password. Change your password after you log in.',
$failed_login_count,
'two-factor'
)
diff --git a/tests/class-two-factor-core.php b/tests/class-two-factor-core.php
index 594d119d..206f0558 100644
--- a/tests/class-two-factor-core.php
+++ b/tests/class-two-factor-core.php
@@ -808,9 +808,10 @@ public function test_maybe_show_last_login_failure_notice() {
$contents = ob_get_clean();
$this->assertNotEmpty( $contents );
- $this->assertStringNotContainsString( '1 times', $contents );
- $this->assertStringContainsString( 'attempted to login', $contents );
- $this->assertStringContainsString( 'without providing a valid two factor token', $contents );
+ // A single failure uses the singular form; assert the plural is absent, since
+ // "attempt" alone also matches "attempts".
+ $this->assertStringContainsString( '1 failed verification code attempt on this account', $contents );
+ $this->assertStringNotContainsString( 'failed verification code attempts', $contents );
// 5 failed login attempts 5 hours ago - User should be informed.
$five_hours_ago = time() - 5 * HOUR_IN_SECONDS;
@@ -821,10 +822,36 @@ public function test_maybe_show_last_login_failure_notice() {
$contents = ob_get_clean();
$this->assertNotEmpty( $contents );
- $this->assertStringContainsString( '5 times', $contents );
+ $this->assertStringContainsString( '5 failed verification code attempts on this account', $contents );
$this->assertStringContainsString( human_time_diff( $five_hours_ago ), $contents );
}
+ /**
+ * Test that the login failure notice uses calm, informational language.
+ *
+ * @covers Two_Factor_Core::maybe_show_last_login_failure_notice()
+ */
+ public function test_login_failure_notice_language_is_calm_and_informational() {
+ $user = $this->get_dummy_user();
+ $one_minute_ago = time() - MINUTE_IN_SECONDS;
+ update_user_meta( $user->ID, Two_Factor_Core::USER_FAILED_LOGIN_ATTEMPTS_KEY, 3 );
+ update_user_meta( $user->ID, Two_Factor_Core::USER_RATE_LIMIT_KEY, $one_minute_ago );
+
+ ob_start();
+ Two_Factor_Core::maybe_show_last_login_failure_notice( $user );
+ $contents = ob_get_clean();
+
+ $this->assertStringNotContainsString( 'WARNING', $contents );
+ $this->assertStringNotContainsString( "wasn't you", $contents );
+ $this->assertStringContainsString( 'failed verification code', $contents );
+ $this->assertStringContainsString( 'someone else may know your password', $contents );
+ $this->assertStringContainsString( 'Change your password after you log in', $contents );
+ $this->assertStringContainsString(
+ 'The last attempt was ' . human_time_diff( $one_minute_ago ) . ' ago',
+ $contents
+ );
+ }
+
/**
* Test no reset notice when no errors.
*