| @@ -862,8 +862,15 @@ | ||
| 862 | 862 | 'warning' |
| 863 | 863 | ); |
| 864 | 864 | } |
| 865 | 865 | |
| 866 | + // This rejection is ours, not a wrong password. wp_authenticate() | |
| 867 | + // still fires wp_login_failed for it, and until 2.11.0 that counted | |
| 868 | + // the blocked attempt as one more failure, which rewrote the row's | |
| 869 | + // status and produced a fresh lockout, with its critical entry and | |
| 870 | + // its email, on every POST made during the lockout (S8). | |
| 871 | + add_filter( 'vigilante_skip_failed_login_count', '__return_true' ); | |
| 872 | + | |
| 866 | 873 | return new WP_Error( |
| 867 | 874 | 'vigilante_lockout', |
| 868 | 875 | sprintf( |
| 869 | 876 | /* translators: %d: Minutes remaining */ |
| @@ -933,8 +940,15 @@ | ||
| 933 | 940 | // Get failed attempts in the last hour |
| 934 | 941 | $failed_count = $this->database->get_failed_attempt_count( $ip, 60 ); |
| 935 | 942 | |
| 936 | 943 | if ( $failed_count >= $max_attempts ) { |
| 944 | + // Already locked out: every further POST during the lockout used to | |
| 945 | + // write another critical entry and send another email (S8). The | |
| 946 | + // lockout itself is what check_lockout() enforces; nothing to add. | |
| 947 | + if ( $this->database->is_locked_out( $ip ) ) { | |
| 948 | + return; | |
| 949 | + } | |
| 950 | + | |
| 937 | 951 | // Calculate lockout duration with increment |
| 938 | 952 | if ( ! empty( $this->options['lockout_increment'] ) ) { |
| 939 | 953 | $previous_lockouts = $this->get_previous_lockout_count( $ip ); |
| 940 | 954 | $lockout_duration = min( |
| @@ -1116,11 +1130,13 @@ | ||
| 1116 | 1130 | // Login Security |
| 1117 | 1131 | 'vigilante_lockout', |
| 1118 | 1132 | // User Security |
| 1119 | 1133 | 'vigilante_force_reset', |
| 1120 | - 'pending_approval', | |
| 1121 | - 'email_not_verified', | |
| 1122 | - 'session_limit_exceeded', | |
| 1134 | + // pending_approval, email_not_verified and session_limit_exceeded | |
| 1135 | + // are deliberately NOT here since 2.11.0: they are only raised once | |
| 1136 | + // the password is correct, so letting them through told an | |
| 1137 | + // unauthenticated visitor which accounts exist (S10). Those users | |
| 1138 | + // learn their status from the registration and verification emails. | |
| 1123 | 1139 | // Two-Factor Email |
| 1124 | 1140 | 'no_code', |
| 1125 | 1141 | 'code_expired', |
| 1126 | 1142 | 'code_used', |
| @@ -1178,14 +1194,10 @@ | ||
| 1178 | 1194 | // Note: this fallback won't match on translated sites — the |
| 1179 | 1195 | // code-based check above is the locale-safe path. |
| 1180 | 1196 | $allowed_patterns = array( |
| 1181 | 1197 | 'vigilante_lockout', |
| 1182 | - 'Account pending', | |
| 1183 | - 'pending_approval', | |
| 1184 | - 'email_not_verified', | |
| 1185 | - 'verify your email', | |
| 1186 | - 'session_limit', | |
| 1187 | - 'too many active', | |
| 1198 | + // The pending-approval, unverified-email and session-limit strings | |
| 1199 | + // were removed in 2.11.0 for the same reason as their codes above (S10). | |
| 1188 | 1200 | 'verification code', |
| 1189 | 1201 | 'authenticator app', |
| 1190 | 1202 | 'two-factor', |
| 1191 | 1203 | 'grace period', |
| @@ -1210,15 +1222,15 @@ | ||
| 1210 | 1222 | * @return array |
| 1211 | 1223 | */ |
| 1212 | 1224 | public function remove_shake_errors( $codes ) { |
| 1213 | 1225 | // Keep shake for Vigilante-specific errors that indicate real problems |
| 1214 | - // Do NOT include 2FA codes - the form transition should be smooth | |
| 1226 | + // Do NOT include 2FA codes - the form transition should be smooth. | |
| 1227 | + // The three account-status codes are not here either since 2.11.0: a | |
| 1228 | + // shake that only plays for existing accounts is the same tell as the | |
| 1229 | + // message it replaced (S10). | |
| 1215 | 1230 | return array( |
| 1216 | 1231 | 'vigilante_lockout', |
| 1217 | 1232 | 'vigilante_force_reset', |
| 1218 | - 'pending_approval', | |
| 1219 | - 'email_not_verified', | |
| 1220 | - 'session_limit_exceeded', | |
| 1221 | 1233 | ); |
| 1222 | 1234 | } |
| 1223 | 1235 | |
| 1224 | 1236 | |
| @@ -1287,9 +1299,9 @@ | ||
| 1287 | 1299 | __( 'Failed attempts', 'vigilante' ) => (string) $attempts, |
| 1288 | 1300 | __( 'Lockout duration', 'vigilante' ) => ceil( $duration / 60 ) . ' ' . __( 'minutes', 'vigilante' ), |
| 1289 | 1301 | __( 'Date/Time', 'vigilante' ) => wp_date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ) ), |
| 1290 | 1302 | ) ); |
| 1291 | - $body .= Vigilante_Email_Template::button( admin_url( 'admin.php?page=vigilante&tab=login' ), __( 'View lockouts', 'vigilante' ) ); | |
| 1303 | + $body .= Vigilante_Email_Template::button( admin_url( 'admin.php?page=vigilante&tab=login#vigilante-section-login-status' ), __( 'View lockouts', 'vigilante' ) ); | |
| 1292 | 1304 | |
| 1293 | 1305 | Vigilante_Email_Template::send( $to, $subject, __( 'Login lockout triggered', 'vigilante' ), $body, true ); |
| 1294 | 1306 | } |
| 1295 | 1307 | |
| @@ -1537,43 +1549,8 @@ | ||
| 1537 | 1549 | public function get_locked_out_ips() { |
| 1538 | 1550 | return $this->database->get_locked_out_ips(); |
| 1539 | 1551 | } |
| 1540 | 1552 | |
| 1541 | - /** | |
| 1542 | - * Get login statistics | |
| 1543 | - * | |
| 1544 | - * @param int $days Days to look back. | |
| 1545 | - * @return array | |
| 1546 | - */ | |
| 1547 | - public function get_statistics( $days = 7 ) { | |
| 1548 | - global $wpdb; | |
| 1549 | - | |
| 1550 | - $table = esc_sql( $this->database->get_login_attempts_table() ); | |
| 1551 | - $since = gmdate( 'Y-m-d H:i:s', strtotime( "-{$days} days" ) ); | |
| 1552 | - | |
| 1553 | - // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared | |
| 1554 | - $stats = $wpdb->get_row( | |
| 1555 | - $wpdb->prepare( | |
| 1556 | - "SELECT | |
| 1557 | - COUNT(CASE WHEN status = 'failed' THEN 1 END) as failed_attempts, | |
| 1558 | - COUNT(CASE WHEN status = 'lockout' THEN 1 END) as lockouts, | |
| 1559 | - COUNT(DISTINCT ip_address) as unique_ips, | |
| 1560 | - COUNT(DISTINCT username) as unique_usernames | |
| 1561 | - FROM `{$table}` | |
| 1562 | - WHERE last_attempt >= %s", | |
| 1563 | - $since | |
| 1564 | - ), | |
| 1565 | - ARRAY_A | |
| 1566 | - ); | |
| 1567 | - // phpcs:enable | |
| 1568 | - | |
| 1569 | - return $stats ? $stats : array( | |
| 1570 | - 'failed_attempts' => 0, | |
| 1571 | - 'lockouts' => 0, | |
| 1572 | - 'unique_ips' => 0, | |
| 1573 | - 'unique_usernames' => 0, | |
| 1574 | - ); | |
| 1575 | - } | |
| 1576 | 1553 | } |
| 1577 | 1554 | |
| 1578 | 1555 | /** |
| 1579 | 1556 | * Disabled XML-RPC Server class |