how to use setFormatter for supporting a password field that isn't changing

how to use setFormatter for supporting a password field that isn't changing

crcucbcrcucb Posts: 63Questions: 19Answers: 0

I have a password field that is stored has a hash in sql. On my datatable editor form, I have two client side fields used for setting a new password. In presubmit, I check the values of the client side password and if they both match, then I set the field in the datatable to the new password.

The issue I am having is in the server side php. I want to ignore the field if it's empty, if it's not empty then set the hash, I am getting an Undefined constant "NO_FORMATTER_APPLIED"

Field::inst( 'UserPassword' )
->getFormatter( function () {
return ''; // Return empty string on read
} )

          ->set( Field::SET_BOTH ) // Allow setting on create and edit
            ->get( false ) // Do not send password hash to client
             ->setFormatter( function ( $val, $data, $field ) {
                if ( ! empty( $val ) ) { // Check if a new password was provided
                    return password_hash( $val, PASSWORD_DEFAULT ); // Hash and return new password
                }
                return Field::NO_FORMATTER_APPLIED; // Keep existing password if not provided
            } ) ,

Answers

  • allanallan Posts: 64,829Questions: 1Answers: 10,731 Site admin
    Answer ✓

    Hi,

    Don't use setFormatter for this - that's for transforming a field's value - i.e. it is always written. Instead, use preCreate and preEdit server-side events Check if a new value was submitted, and if it was use ->setValue() to set the hashed value as the one to write.

    It was a while ago, but I wrote some example code in a 2015 blog post.

    Allan

  • rf1234rf1234 Posts: 3,164Questions: 92Answers: 436
    Answer ✓

    In addition to Allan's comment maybe my solution for this could be helpful.

    I also check whether a user has ever been active. And that is the case if there is a saved password in the database.

    The issue I am having is in the server side php. I want to ignore the field if it's empty, if it's not empty then set the hash,

    The custom setFormatter handles that for me.

    Field::inst( 'user.password' )
        ->validator( function ( $val, $data, $opts ) {
            return validatorPassword($val);
        } )
        ->getFormatter( function ( $val, $data, $opts ) {
            return '';                   
        } )
        ->setFormatter( function($val, $data, $opts) {
            if ( $val <= '') {
                return '';
            }
            return password_hash($val, PASSWORD_DEFAULT, ['cost' => 14]);
        }),
    Field::inst( 'user.password AS repeatPassword' )->set( false )
        ->validator( function ( $val, $data, $opts ) {
            return validatorPassword($val, $data['user']);
        } )
        ->getFormatter( function ( $val, $data, $opts ) {
            return '';                   
        } ),
    Field::inst( 'user.password AS ever_active' )->set( false )   //if the password is empty the user has never been active    
        ->getFormatter( function($val, $data, $opts) {
            if ( $val <= " ") {
                return 0;
            }
            return 1;
        }),
    
  • rf1234rf1234 Posts: 3,164Questions: 92Answers: 436
    Answer ✓

    Don't use setFormatter for this - that's for transforming a field's value - i.e. it is always written.

    Sorry, I forgot to address Allan's valid point!

    How am I achieving that the password is only written if
    - both passwords are not empty (i.e. the user really wants to change the password)
    - both passwords are identical and valid?

    That is done with the validators!

    Even if the first validator returns true, the second validator for the repeat password will return an error message, if the first password was empty and the repeat password was filled. That means that the setFormatter will be irrelevant if the validators return errors.

    Here is part of the code that achieves that:

    function validatorPassword (&$val, &$data = null ) {    
        if ( ! is_null($data) ) {
            $repeatPassword = true;
            if ($val <= '' && $data['password'] <= '') {
                return true;
            }
        } else {
            $repeatPassword = false;
            if ($val <= '') {
                return true;
            }
        }
        
        if ($repeatPassword) {
            if ($data['password'] <= '') {
                return "Please enter new password above first!";
            }
            if ($data['password'] !== $val) {
                return "The two passwords don't match";
            }
        }
    }
    
  • rf1234rf1234 Posts: 3,164Questions: 92Answers: 436
    Answer ✓

    Oh, it is too long ago. Sorry!

    I also need the event handlers Allan was talking about to make sure no updates happen if the password-field isn't filled on the client side. That's in addition to my code above.

    ->on('validatedCreate', function ( $editor, $values ) {
        if ($values['user']['password'] <= '') {
            $editor->field('user.password')->set( false );
        }
    })
    ->on('validatedEdit', function ( $editor, $id, $values ) {     
        if ($values['user']['password'] <= '') {
           $editor->field('user.password')->set( false );
        }
    })
    
Sign In or Register to comment.