-
-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Adds password history support to passwordPolicy #3102
Conversation
@bhaskaryasa updated the pull request - view changes |
@@ -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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
@bhaskaryasa updated the pull request - view changes |
@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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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': |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 = { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 = { |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
@bhaskaryasa updated the pull request - view changes |
@bhaskaryasa updated the pull request - view changes |
@bhaskaryasa updated the pull request - view changes |
- renamed field "_old_passwords" to "_password_history" - simplified passwordHistory test cases without using emailAdapter
@bhaskaryasa updated the pull request - view changes |
@cherukumilli - Please review the update. I retained one test case for passwordHistory that uses emailAdapter through requestResetPassword but redone others. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bhaskaryasa
LGTM
@bhaskaryasa updated the pull request - view changes |
* password history support in passwordPolicy * Refactor RestWrite.transformUser * fix eslint issues
* password history support in passwordPolicy * Refactor RestWrite.transformUser * fix eslint issues
@bhaskaryasa I have one questions. When User query, it return a result with |
This has been overlooked. Do yo want to make a PR to rectify it? |
@flovilmart OK, I'll make a PR tonight. :) |
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.