jonathan-dejong / simple-jwt-authentication

Extends the WP REST API using JSON Web Tokens Authentication as an authentication method.
GNU General Public License v3.0
87 stars 25 forks source link

Make use of get_password_reset_key() in reset_password() method #23

Open mathiasastrom opened 6 years ago

mathiasastrom commented 6 years ago

Great plugin, saved me a great deal of pain.

Experienced buggy behaviour when I tried to reset the password through the WP REST api callback, the reset key was never updating and it was stuck in the same corrupt key. Much needed logic that was in your reset_password() method already exists in the core function: get_password_reset_key() - The function was added in WP 4.4 and should be safe to use I believe. (Depending on which WP versions you are supporting)

Feel free to use/test my modified method below:

/**
 * Endpoint for requesting a password reset link.
 * This is a slightly modified version of what WP core uses.
 *
 * @param object $request The request object that come in from WP Rest API.
 * @since 1.0
 */
public function reset_password( $request ) {
    $username = $request->get_param( 'username' );
    if ( ! $username ) {
        return array(
            'code' => 'jwt_auth_invalid_username',
            'message' => __( '<strong>Error:</strong> Username or email not specified.', 'simple-jwt-authentication' ),
            'data' => array(
                'status' => 403,
            ),
        );
    } elseif ( strpos( $username, '@' ) ) {
        $user_data = get_user_by( 'email', trim( $username ) );
    } else {
        $user_data = get_user_by( 'login', trim( $username ) );
    }

    global $wpdb, $current_site;

    do_action( 'lostpassword_post' );
    if ( ! $user_data ) {
        return array(
            'code' => 'jwt_auth_invalid_username',
            'message' => __( '<strong>Error:</strong> Invalid username.', 'simple-jwt-authentication' ),
            'data' => array(
                'status' => 403,
            ),
        );
    }

    // redefining user_login ensures we return the right case in the email
    $user_login = $user_data->user_login;
    $user_email = $user_data->user_email;

    $key = get_password_reset_key( $user_data );

    // Some simple error management
    if( is_wp_error( $key ) || !$key ){

        $default_err_response = array(
            'code' => 'jwt_auth_reset_password_not_allowed',
            'message' => __( '<strong>Error:</strong> Resetting password is not allowed.', 'simple-jwt-authentication' ),
            'data' => array(
                'status' => 403,
            )
        );

        // Check for "falsy" results, no WP_Error objects
        if( !is_wp_error( $key ) && !$key ){
            return $default_err_response;
        }

        // Get the error code
        $error_code = $key->get_error_code();

        // Lets check for some normal error messages you might recieve from get_password_reset_key() function
        if( $error_code === 'no_password_reset' ){
            // Check if resetting the password for the user is allowed
            return $default_err_response;
        }elseif( $error_code === 'no_password_key_update' ){
            // Failed to update the reset key to the database
            return array(
                'code' => 'jwt_auth_reset_password_no_password_key_update',
                'message' => __( '<strong>Error:</strong> Could not save password reset key to database.', 'simple-jwt-authentication' ),
                'data' => array(
                    'status' => 403,
                )
            );
        }else{
            // Fallback error, maybe WP added an new error code that we do not support...
            return $default_err_response;
        }
    }

    $message = __( 'Someone requested that the password be reset for the following account:' ) . "\r\n\r\n";
    $message .= network_home_url( '/' ) . "\r\n\r\n";
    $message .= sprintf( __( 'Username: %s' ), $user_login ) . "\r\n\r\n";
    $message .= __( 'If this was a mistake, just ignore this email and nothing will happen.' ) . "\r\n\r\n";
    $message .= __( 'To reset your password, visit the following address:' ) . "\r\n\r\n";
    $message .= '<' . network_site_url( "wp-login.php?action=rp&key=$key&login=" . rawurlencode( $user_login ), 'login' ) . ">\r\n";

    if ( is_multisite() ) {
        $blogname = $GLOBALS['current_site']->site_name;
    } else {
        // The blogname option is escaped with esc_html on the way into the database in sanitize_option
        // we want to reverse this for the plain text arena of emails.
        $blogname = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
    }

    $title = sprintf( __( '[%s] Password Reset' ), $blogname );

    $title = apply_filters( 'retrieve_password_title', $title );
    $message = apply_filters( 'retrieve_password_message', $message, $key );

    if ( $message && ! wp_mail( $user_email, $title, $message ) ) {
        wp_die( __( 'The e-mail could not be sent.' ) . "<br />\n" . __( 'Possible reason: your host may have disabled the mail() function...' ) );
    }

    return array(
        'code' => 'jwt_auth_password_reset',
        'message' => __( '<strong>Success:</strong> an email for selecting a new password has been sent.', 'simple-jwt-authentication' ),
        'data' => array(
            'status' => 200,
        ),
    );
}
jonathan-dejong commented 6 years ago

Hi Mathias,

Thanks for pointing this out! When I added the reset endpoint I just looked at what was in core at the time and how others had solved it and found that it worked but was a little bit buggy. I did not know about this new function!

I don't think compatibility is an issue since this plugin is aimed towards new development with WordPress (REST) :)

Would you mind turning your fix into a PR?