time() ) { $minutes = (int) max( 1, ceil( ( $data['until'] - time() ) / MINUTE_IN_SECONDS ) ); return new \WP_Error( 'siteaudithub_locked', sprintf( /* translators: %d: minutes */ _n( 'Error: Too many failed login attempts. Please try again in %d minute.', 'Error: Too many failed login attempts. Please try again in %d minutes.', $minutes, 'siteaudithub' ), $minutes ) ); } return $user; } /** * Count a failed attempt. * * @param string $username Submitted username or email. */ public static function failed( $username ) { $key = self::key( $username ); if ( '' === $key ) { return; } $data = self::get( $key ); if ( $data['until'] > time() ) { return; // Already locked; do not extend. } $max = max( 1, (int) Settings::get( 'limit_login_attempts' ) ); $seconds = max( 1, (int) Settings::get( 'limit_login_minutes' ) ) * MINUTE_IN_SECONDS; ++$data['count']; if ( $data['count'] >= $max ) { $data['until'] = time() + $seconds; $data['count'] = 0; } set_transient( self::PREFIX . $key, $data, $seconds ); } /** * Forget failures after a successful login. * * @param string $user_login Login. */ public static function succeeded( $user_login ) { $key = self::key( $user_login ); if ( '' !== $key ) { delete_transient( self::PREFIX . $key ); } } /** * Remove every lockout (used when the feature is switched off). */ public static function clear_all() { global $wpdb; $like = $wpdb->esc_like( '_transient_' . self::PREFIX ) . '%'; $keys = $wpdb->get_col( $wpdb->prepare( 'SELECT option_name FROM %i WHERE option_name LIKE %s LIMIT 5000', $wpdb->options, $like ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Transients have no lookup-by-prefix API. foreach ( (array) $keys as $name ) { delete_transient( substr( $name, strlen( '_transient_' ) ) ); } } /** * Stored attempt data. * * @param string $key Key. * @return array{count:int,until:int} */ private static function get( $key ) { $data = get_transient( self::PREFIX . $key ); return array( 'count' => is_array( $data ) && isset( $data['count'] ) ? (int) $data['count'] : 0, 'until' => is_array( $data ) && isset( $data['until'] ) ? (int) $data['until'] : 0, ); } /** * Anonymous key for "username + visitor". * * @param string $username Username or email. * @return string Empty when there is no username. */ private static function key( $username ) { $username = strtolower( trim( (string) $username ) ); if ( '' === $username ) { return ''; } // Count "admin" and its email address as the same account. if ( is_email( $username ) ) { $user = get_user_by( 'email', $username ); if ( $user ) { $username = strtolower( $user->user_login ); } } $ip = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : ''; return substr( hash_hmac( 'sha256', $username . '|' . $ip, wp_salt( 'auth' ) ), 0, 32 ); } }