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. *