Using Editor with Password fields and Salt

Using Editor with Password fields and Salt

chris.cavagechris.cavage Posts: 46Questions: 12Answers: 0

I am using Editor to add new users to a website. I have a hidden salt field that holds a random string. I obviously also have a password field. The password field is a required field, of course.

I have everything working properly when a new user is added. The salt is added to the db and the password is encrypted using a function in setFormatter().

If a user row is edited, I only want the password to use setFormatter() if the password field is changed. I also want the salt to be added if the password is updated, otherwise, don't do anything to the password and salt fields in the db.

The salt is a hidden field in the editor. Right now my salt default value is created by using this code:

$salt = substr(str_shuffle("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), 0, 20);

Here's what I have now:

// Build our Editor instance and process the data coming from _POST
Editor::inst( $db, 'users', 'id' )
    ->fields(
        Field::inst( 'first_name' )
            ->validator( 'Validate::notEmpty' ),
        Field::inst( 'middle_name' ),
        Field::inst( 'last_name' )
            ->validator( 'Validate::notEmpty' ),
        Field::inst( 'username' )
            ->validator( 'Validate::notEmpty' )
            ->validator( 'Validate::unique' ),
        Field::inst( 'salt' )
            ->validator( 'Validate::notEmpty' ),
        Field::inst( 'password' )
            ->validator( 'Validate::notEmpty' )
            ->setFormatter( function ( $val ) {
                return hash('sha256',  $val .  $_POST['data'][0]['salt']);
            })
    )
    ->where( $key = "company_id", $value = $company_id, $op = "=" )
    ->process( $_POST )
    ->json();

One separate issue I have is on this line:

->setFormatter( function ( $val ) {
                return hash('sha256',  $val .  $_POST['data'][0]['salt']);
            })

I am trying to use the hidden salt value to encrypt the password. This code works on inserting a new user, but when I try to edit it, I get a system error:

<b>Notice</b>: Undefined offset: 0

This question has an accepted answers - jump to answer

Answers

  • allanallan Posts: 63,834Questions: 1Answers: 10,518 Site admin

    If a user row is edited, I only want the password to use setFormatter() if the password field is changed.

    Have a look at Editor's server-side events. Specifically, this blog post introducing them describes exactly what you are looking for.

    Allan

  • chris.cavagechris.cavage Posts: 46Questions: 12Answers: 0

    Thanks, I am making some progress.

    Can you answer a question for me?

    How can I make my password field required ONLY if a new user is being created? Because I'm returning a blank password field when editing, I don't want that field required. If it is, then the user will have a different password every time something else is edited.

    Field::inst( 'salt' )
                ->validator( 'Validate::notEmpty' ),
            Field::inst( 'password' )
                ->setFormatter( function ( $val, $data ) {
                    return hash('sha256',  $val . $data['salt']  );
                })
                ->getFormatter( function () {
                    return '';
                })
        )
        ->where( $key = "company_id", $value = $company_id, $op = "=" )
        ->on( 'preEdit', function ( $e, $id, $values ) {
            if ( $values['password'] === '' ) {
                $e->field( 'password' )->set( false );
                $e->field( 'salt' )->set( false );
            } 
        })
        ->process( $_POST )
        ->json();
    
    
  • chris.cavagechris.cavage Posts: 46Questions: 12Answers: 0

    I did it! I think this is right:

    editor.on('onPreSubmit', function(e,obj,action){
            //--- Compare Password and Confirm fields. ---//
            
            if (action == 'create') {
                if ( this.get( 'password' ) === '' ) {
                this.field('password').error('Password is required if creating a new user!');
                return false;
                }
            }
                    
            
             if ( $.trim(this.get('password')) !== $.trim(this.get('repassword')) ) {
                 this.field('password').error('Passwords do not match!');
                 return false;
             }
            
            return true;
        });
    
  • allanallan Posts: 63,834Questions: 1Answers: 10,518 Site admin
    Answer ✓

    That looks good. You could also use server-side validation for it (which personally I would recommend since you can't trust data from the client). The preCreate and preEdit server-side events can be used to alter how the data is set or not.

    Allan

  • chris.cavagechris.cavage Posts: 46Questions: 12Answers: 0

    Thanks!

  • rf1234rf1234 Posts: 3,028Questions: 88Answers: 422
    edited December 2016

    Thanks guys, this post helped me a lot!
    I had a little trouble figuring it out but eventually I got it working. It would be great to have a more detailed description of the structure of the parameters that are passed into
    "->on('preCreate', function ( $editor, $values )" though.
    In this example I hash the answers to security questions. The answers cannot be edited. You can only create or delete a question-and-answer pairs which probably makes the solution a bit easier. Here it is with "salt" (saved in db) and "PEPPER" (constant)" plus the constant but individual user id:

    function tblUserSec(&$db, &$lang) {
        if ( ! isset($_POST['user']) || ! is_numeric($_POST['user']) ) {
            echo json_encode( [ "data" => [] ] );
        } else {
            if ($lang === 'de') {     
                $msg[0] = 'Bitte Sicherheitsfrage angeben.';
                $msg[1] = 'Bitte Antwort eintragen.';
            } else {
                $msg[0] = 'Please provide a security question.';
                $msg[1] = 'Please enter an answer.';
            }
            Editor::inst( $db, 'usersecquestion' )
            ->field(
                Field::inst( 'usersecquestion.user_id' )->set(Field::SET_CREATE),
                Field::inst( 'usersecquestion.question' )->validator( 'Validate::notEmpty' , array('message' => $msg[0]) ),
                Field::inst( 'usersecquestion.answer' )->validator( 'Validate::notEmpty' , array('message' => $msg[1]) ),
                Field::inst( 'usersecquestion.salt' )-> set(Field::SET_CREATE),                                                   
                Field::inst( 'usersecquestion.creator_id' )-> set(Field::SET_CREATE)
        )
            ->leftJoin( 'user', 'user.id', '=', 'usersecquestion.user_id' )
            ->where( 'usersecquestion.user_id', $_POST['user'] )
            ->on('preCreate', function ( $editor, $values ) {
                $userUser = filter_var($_POST['user']);
                $editor            
                    ->field('usersecquestion.user_id')
                    ->setValue($userUser);
                $currentUser = filter_var($_SESSION['id']);
                $editor            
                    ->field('usersecquestion.creator_id')
                    ->setValue($currentUser);
                $saltDb = generateRandomToken(35);
                $salt = $saltDb.(string)$userUser.PEPPER;
                $answerEncrypted = sha512Encode($values['usersecquestion']['answer'], $salt, 2);
                $editor            
                    ->field('usersecquestion.answer')
                    ->setValue($answerEncrypted);
                 $editor            
                    ->field('usersecquestion.salt')
                    ->setValue($saltDb);
                
            })
            ->process($_POST)
            ->json();
        }
    }
    
This discussion has been closed.