Search code examples
wordpresstemplateswoocommercepasswordswordpress-hook

Change password without entering current one on My account > edit account in WooCommerce


I would like to disable the "current password" field when a user tries to change their password. Often users have difficulty with passwords, when forgotten it is not possible to ask for the current password.

The user edit form is this: https://woocommerce.github.io/code-reference/files/woocommerce-templates-myaccount-form-edit-account.html

I tried deleting the content relating to line 50 to 53 which would be the current password field, but this is not enough, the current password is still needed.

Then I thought about working around the required attribute but it doesn't exist in the form, apparently wordpress asks for validation in another way.

How could the problem be solved?


Template name: form-edit-account.php

<?php
/**
 * Edit account form
 *
 * This template can be overridden by copying it to yourtheme/woocommerce/myaccount/form-edit-account.php.
 *
 * HOWEVER, on occasion WooCommerce will need to update template files and you
 * (the theme developer) will need to copy the new files to your theme to
 * maintain compatibility. We try to do this as little as possible, but it does
 * happen. When this occurs the version of the template file will be bumped and
 * the readme will list any important changes.
 *
 * @see https://docs.woocommerce.com/document/template-structure/
 * @package WooCommerce\Templates
 * @version 3.5.0
 */

defined( 'ABSPATH' ) || exit;

do_action( 'woocommerce_before_edit_account_form' ); ?>

<form class="woocommerce-EditAccountForm edit-account" action="" method="post" <?php do_action( 'woocommerce_edit_account_form_tag' ); ?> >

    <?php do_action( 'woocommerce_edit_account_form_start' ); ?>

    <p class="woocommerce-form-row woocommerce-form-row--first form-row form-row-first">
        <label for="account_first_name"><?php esc_html_e( 'First name', 'woocommerce' ); ?>&nbsp;<span class="required">*</span></label>
        <input type="text" class="woocommerce-Input woocommerce-Input--text input-text" name="account_first_name" id="account_first_name" autocomplete="given-name" value="<?php echo esc_attr( $user->first_name ); ?>" />
    </p>
    <p class="woocommerce-form-row woocommerce-form-row--last form-row form-row-last">
        <label for="account_last_name"><?php esc_html_e( 'Last name', 'woocommerce' ); ?>&nbsp;<span class="required">*</span></label>
        <input type="text" class="woocommerce-Input woocommerce-Input--text input-text" name="account_last_name" id="account_last_name" autocomplete="family-name" value="<?php echo esc_attr( $user->last_name ); ?>" />
    </p>
    <div class="clear"></div>

    <p class="woocommerce-form-row woocommerce-form-row--wide form-row form-row-wide">
        <label for="account_display_name"><?php esc_html_e( 'Display name', 'woocommerce' ); ?>&nbsp;<span class="required">*</span></label>
        <input type="text" class="woocommerce-Input woocommerce-Input--text input-text" name="account_display_name" id="account_display_name" value="<?php echo esc_attr( $user->display_name ); ?>" /> <span><em><?php esc_html_e( 'This will be how your name will be displayed in the account section and in reviews', 'woocommerce' ); ?></em></span>
    </p>
    <div class="clear"></div>

    <p class="woocommerce-form-row woocommerce-form-row--wide form-row form-row-wide">
        <label for="account_email"><?php esc_html_e( 'Email address', 'woocommerce' ); ?>&nbsp;<span class="required">*</span></label>
        <input type="email" class="woocommerce-Input woocommerce-Input--email input-text" name="account_email" id="account_email" autocomplete="email" value="<?php echo esc_attr( $user->user_email ); ?>" />
    </p>

    <fieldset>
        <legend><?php esc_html_e( 'Password change', 'woocommerce' ); ?></legend>

        <p class="woocommerce-form-row woocommerce-form-row--wide form-row form-row-wide">
            <label for="password_current"><?php esc_html_e( 'Current password (leave blank to leave unchanged)', 'woocommerce' ); ?></label>
            <input type="password" class="woocommerce-Input woocommerce-Input--password input-text" name="password_current" id="password_current" autocomplete="off" />
        </p>
        <p class="woocommerce-form-row woocommerce-form-row--wide form-row form-row-wide">
            <label for="password_1"><?php esc_html_e( 'New password (leave blank to leave unchanged)', 'woocommerce' ); ?></label>
            <input type="password" class="woocommerce-Input woocommerce-Input--password input-text" name="password_1" id="password_1" autocomplete="off" />
        </p>
        <p class="woocommerce-form-row woocommerce-form-row--wide form-row form-row-wide">
            <label for="password_2"><?php esc_html_e( 'Confirm new password', 'woocommerce' ); ?></label>
            <input type="password" class="woocommerce-Input woocommerce-Input--password input-text" name="password_2" id="password_2" autocomplete="off" />
        </p>
    </fieldset>
    <div class="clear"></div>

    <?php do_action( 'woocommerce_edit_account_form' ); ?>

    <p>
        <?php wp_nonce_field( 'save_account_details', 'save-account-details-nonce' ); ?>
        <button type="submit" class="woocommerce-Button button" name="save_account_details" value="<?php esc_attr_e( 'Save changes', 'woocommerce' ); ?>"><?php esc_html_e( 'Save changes', 'woocommerce' ); ?></button>
        <input type="hidden" name="action" value="save_account_details" />
    </p>

    <?php do_action( 'woocommerce_edit_account_form_end' ); ?>
</form>

<?php do_action( 'woocommerce_after_edit_account_form' ); ?>

Solution

  • Note: as indicated it is better not to make any changes as this will make the application less secure. However, to answer your question:

    While the output from the current password field can be easily modified by overwriting the template file, the validation is hard coded and can be found in the /includes/class-wc-form-handler.php file.

    To bypass the validation we have to pass 2 error messages, namely:

    • Please enter your current password
    • Your current password is incorrect

    To get around this, we are not going to use the real current password, but we are going to create/add extra custom data, and on that basis allow the user to change his password.


    1) To bypass the first notice you will have to overwrite the /myaccount/form-edit-account.php template file.

    Replace line 50 - 53 @version 3.5.0

    <p class="woocommerce-form-row woocommerce-form-row--wide form-row form-row-wide">
        <label for="password_current"><?php esc_html_e( 'Current password (leave blank to leave unchanged)', 'woocommerce' ); ?></label>
        <input type="password" class="woocommerce-Input woocommerce-Input--password input-text" name="password_current" id="password_current" autocomplete="off" />
    </p>
    

    With

    <?php
    // Get userID
    $user_id = $user->ID;
    
    // Generate salt
    $salt = md5( openssl_random_pseudo_bytes( 32, $cstrong ) . wp_generate_password( 32, true, true ) );
    $enc = $user_id . '::' . crypt( $user_id, $salt );
    
    // NOT empty
    if ( ! empty ( $enc ) ) {
        update_user_meta( $user_id, '_enc', $enc );
    }
    
    // Encodes data with MIME base64
    $value = base64_encode( $enc );
    
    // NOT empty
    if ( ! empty ( $value ) ) {
        ?>
        <input type="hidden" name="password_current" id="password_current" value="<?php echo $value; ?>" />
        <?php
    } else {
        ?>
        <p class="woocommerce-form-row woocommerce-form-row--wide form-row form-row-wide">
            <label for="password_current"><?php esc_html_e( 'Current password (leave blank to leave unchanged)', 'woocommerce' ); ?></label>
            <input type="password" class="woocommerce-Input woocommerce-Input--password input-text" name="password_current" id="password_current" autocomplete="off" />
        </p>
        <?php
    }
    ?>
    

    This will replace the current password field with a hidden input field with a specified value. This value is based on the current userID and a salt, which is then saved as user_meta when the page is requested.


    2) To bypass the second notification, you can use the check_password() WordPress filter hook

    /**
     * Filters whether the plaintext password matches the encrypted password.
     *
     * @since 2.5.0
     *
     * @param bool       $check    Whether the passwords match.
     * @param string     $password The plaintext password.
     * @param string     $hash     The hashed password.
     * @param string|int $user_id  User ID. Can be empty.
     */
    function filter_check_password( $check, $password, $hash, $user_id ) {
        // Only for WooCommerce edit-account endpoint
        if ( ! is_wc_endpoint_url( 'edit-account' ) ) return $check;
    
        // Get meta
        $enc = get_user_meta( $user_id, '_enc', true );
    
        // NOT empty
        if ( ! empty ( $enc ) ) {
            // Get parts
            $parts = explode( '::', base64_decode( $password ) );
    
            // Compare user ID and data
            if ( $parts[0] == $user_id && str_contains( $enc, $parts[1] ) ) {
                $check = true;
            }
        }
        
        return $check;
    }
    add_filter( 'check_password', 'filter_check_password', 10, 4 );
    

    This will ensure that instead of using the real current password (which is encrypted), our custom user_meta is used. When it matches, we allow to change the password