Skip to content

Adds password history support to passwordPolicy #3102

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Nov 29, 2016
Merged

Adds password history support to passwordPolicy #3102

merged 4 commits into from
Nov 29, 2016

Conversation

bhaskaryasa
Copy link
Contributor

passwordPolicy.passwordHistory

Optional setting to specify the number of passwords to retain in history and prevent reusing the same during password reset. The maximum value is 20.

a new Array field _old_passwords is added to _User object to store the old passwords (hash values) whenever password is changed.

@facebook-github-bot
Copy link

@bhaskaryasa updated the pull request - view changes

@cherukumilli cherukumilli self-assigned this Nov 23, 2016
@@ -288,6 +288,7 @@ var server = ParseServer({
validatorCallback: (password) => { return validatePassword(password) },
doNotAllowUsername: true, // optional setting to disallow username in passwords
maxPasswordAge: 90, // optional setting in days for password expiry. Login fails if user does not reset the password within this period after signup/last reset.
passwordHistory: 5, // optional setting to prevent reuse of previous n passwords. Maximum value that can be specified is 20. Specifying 0 will not enforce history.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bhaskaryasa
Is the default value 0?
If so, it will be good if you can say that in the Readme.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cherukumilli - It is optional so default is 'undefined' and does not enforce history. Behavior by setting to 0 would be similar. I will update readme accordingly.

@@ -399,8 +399,39 @@ RestWrite.prototype.transformUser = function() {
});
}
}
// check whether password is repeating from specified history
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bhaskaryasa
I think this entire transformUser method should be refactored (even before you changes to it). I think we should use this opportunity to clean it up. WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I too felt its become somewhat big and kind of difficult to comprehend. Too many things going on there, more so after I added passwordPolicy. I will try and refactor it.

@facebook-github-bot
Copy link

@bhaskaryasa updated the pull request - view changes

@bhaskaryasa
Copy link
Contributor Author

@cherukumilli - I have committed changes after some refactoring on transformUser. I hope the PR is good to merge.

@@ -288,6 +288,7 @@ var server = ParseServer({
validatorCallback: (password) => { return validatePassword(password) },
doNotAllowUsername: true, // optional setting to disallow username in passwords
maxPasswordAge: 90, // optional setting in days for password expiry. Login fails if user does not reset the password within this period after signup/last reset.
passwordHistory: 5, // optional setting to prevent reuse of previous n passwords. Maximum value that can be specified is 20. Not specifying it or specifying 0 will not enforce history.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bhaskaryasa
WDYT about using maxPasswordHistory instead of passwordHistory here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. Will change it.

@@ -786,6 +786,7 @@ const mongoObjectToParseObject = (className, mongoObject, schema) => {
case '_email_verify_token_expires_at':
case '_account_lockout_expires_at':
case '_failed_login_count':
case '_old_passwords':
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bhaskaryasa
As you are storing password history in this column name, I think _password_history might be a better name for this variable instead of _old_passwords. It also flows with the entire feature.
WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used this name to avoid conflict with "passwordHistory" name used for configuration which is numeric. As we are changing it to "maxPasswordHistory" now, I can rename _old_passwords to _password_history.


it('should fail if the new password is same as the last password', done => {
const user = new Parse.User();
const emailAdapter = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bhaskaryasa
Do we really need an email adapter here? I think this test case can be simplified to something like:

  it('should fail if the new password is same as the last password', done => {
    reconfigureServer({
      appName: 'passwordPolicy',
      passwordPolicy: {
        passwordHistory: 1
      },
    }).then(() => {
      Parse.User.signUp('user1', 'password1').then(user1 => {
        user1.setPassword('password1');
        return user1.save();
      }).then(() => {
        // cannot set the same password twice
        fail();
      }).catch(err => {
        expect(err.message).to.equal('New password should not be the same as last 1 passwords.');
        expect(err.code).to.equal(142); // 142 might have to be replaced by Parse.Error.xxx
        done();
      });
    });
  });

WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you are right. Somehow I was thinking of only resetPassword as the way to change password in which case you will need to go through emailAdapter. It skipped my mind that password can be changed by the user after signing in. I will change the test cases.

it('should fail if the new password is same as 5th oldest password in history when policy does not allow last 5 passwords', done => {
const user = new Parse.User();
let pwCount = 1;
const emailAdapter = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bhaskaryasa
same comment as in line 1064 above.
I think not using an emailAdapter will simplify this entire test case.

@@ -353,14 +353,16 @@ RestWrite.prototype.transformUser = function() {
if (this.query) {
Copy link
Contributor

@cherukumilli cherukumilli Nov 29, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bhaskaryasa
Pls check the beginning of this method (line 347). There is an if statement like:

  if (this.className !== '_User') {
    return;
  }

Should this return a promise?

I know this is not your code and it is the original code but I think we should correct it if it is incorrect. WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this code simply relies on the fact that then() resolves to SUCCESS if the handler returns undefined. It would be cleaner to return a properly resolved promise.

@@ -369,96 +371,143 @@ RestWrite.prototype.transformUser = function() {
return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bhaskaryasa
On line 370, we have code like:

    if (!this.data.password) {
      return;
    }

I think we should move this code up to the beginning. Also, it should return a promise. WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't move it up because this section only handles password change. This should not impact if other fields are changed without touching the password.

Will add code to return a resolved promise.

Refactor RestWrite.transformUser
@facebook-github-bot
Copy link

@bhaskaryasa updated the pull request - view changes

@facebook-github-bot
Copy link

@bhaskaryasa updated the pull request - view changes

@facebook-github-bot
Copy link

@bhaskaryasa updated the pull request - view changes

- renamed field "_old_passwords" to "_password_history"
- simplified passwordHistory test cases without using emailAdapter
@facebook-github-bot
Copy link

@bhaskaryasa updated the pull request - view changes

@bhaskaryasa
Copy link
Contributor Author

@cherukumilli - Please review the update. I retained one test case for passwordHistory that uses emailAdapter through requestResetPassword but redone others.

Copy link
Contributor

@cherukumilli cherukumilli left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@facebook-github-bot
Copy link

@bhaskaryasa updated the pull request - view changes

@cherukumilli cherukumilli merged commit bd16891 into parse-community:master Nov 29, 2016
rsouzas pushed a commit to back4app/parse-server that referenced this pull request Dec 3, 2016
* password history support in passwordPolicy

* Refactor RestWrite.transformUser

* fix eslint issues
@bhaskaryasa bhaskaryasa deleted the password-policy--history branch December 11, 2016 01:17
Jcarlosjunior pushed a commit to back4app/parse-server that referenced this pull request Dec 13, 2016
* password history support in passwordPolicy

* Refactor RestWrite.transformUser

* fix eslint issues
@yomybaby
Copy link
Contributor

yomybaby commented Apr 3, 2018

@bhaskaryasa I have one questions.

When User query, it return a result with _password_history event without any key :
Is there any reason do not delete _password_history filed on filterSensitiveData function?

@flovilmart
Copy link
Contributor

This has been overlooked. Do yo want to make a PR to rectify it?

@yomybaby
Copy link
Contributor

yomybaby commented Apr 4, 2018

@flovilmart OK, I'll make a PR tonight. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants