diff --git a/README.md b/README.md index b7eab1dacc..188e8d0631 100644 --- a/README.md +++ b/README.md @@ -281,7 +281,7 @@ var server = ParseServer({ passwordPolicy: { // Two optional settings to enforce strong passwords. Either one or both can be specified. // If both are specified, both checks must pass to accept the password - // 1. a RegExp representing the pattern to enforce + // 1. a RegExp object or a regex string representing the pattern to enforce validatorPattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.{8,})/, // enforce password with at least 8 char with at least 1 lower case, 1 upper case and 1 digit // 2. a callback function to be invoked to validate the password validatorCallback: (password) => { return validatePassword(password) }, diff --git a/spec/PasswordPolicy.spec.js b/spec/PasswordPolicy.spec.js index 4f385821ae..cdd6b27484 100644 --- a/spec/PasswordPolicy.spec.js +++ b/spec/PasswordPolicy.spec.js @@ -147,14 +147,14 @@ describe("Password Policy: ", () => { reconfigureServer({ appName: 'passwordPolicy', passwordPolicy: { - validatorPattern: "abc" // string is not a valid setting + validatorPattern: 1234 // number is not a valid setting }, publicServerURL: "http://localhost:8378/1" }).then(() => { fail('passwordPolicy.validatorPattern type test failed'); done(); }).catch(err => { - expect(err).toEqual('passwordPolicy.validatorPattern must be a RegExp.'); + expect(err).toEqual('passwordPolicy.validatorPattern must be a regex string or RegExp object.'); done(); }); }); @@ -197,6 +197,28 @@ describe("Password Policy: ", () => { }) }); + it('signup should fail if password does not conform to the policy enforced using validatorPattern string', (done) => { + const user = new Parse.User(); + reconfigureServer({ + appName: 'passwordPolicy', + passwordPolicy: { + validatorPattern: "^.{8,}" // password should contain at least 8 char + }, + publicServerURL: "http://localhost:8378/1" + }).then(() => { + user.setUsername("user1"); + user.setPassword("less"); + user.set('email', 'user1@parse.com'); + user.signUp().then(() => { + fail('Should have failed as password does not conform to the policy.'); + done(); + }).catch((error) => { + expect(error.code).toEqual(142); + done(); + }); + }) + }); + it('signup should succeed if password conforms to the policy enforced using validatorPattern', (done) => { const user = new Parse.User(); reconfigureServer({ @@ -231,6 +253,40 @@ describe("Password Policy: ", () => { }) }); + it('signup should succeed if password conforms to the policy enforced using validatorPattern string', (done) => { + const user = new Parse.User(); + reconfigureServer({ + appName: 'passwordPolicy', + passwordPolicy: { + validatorPattern: "[!@#$]+" // password should contain at least one special char + }, + publicServerURL: "http://localhost:8378/1" + }).then(() => { + user.setUsername("user1"); + user.setPassword("p@sswrod"); + user.set('email', 'user1@parse.com'); + user.signUp().then(() => { + Parse.User.logOut().then(() => { + Parse.User.logIn("user1", "p@sswrod").then(function () { + done(); + }).catch((err) => { + jfail(err); + fail("Should be able to login"); + done(); + }); + }).catch((error) => { + jfail(error); + fail('logout should have succeeded'); + done(); + }); + }).catch((error) => { + jfail(error); + fail('Signup should have succeeded as password conforms to the policy.'); + done(); + }); + }) + }); + it('signup should fail if password does not conform to the policy enforced using validatorCallback', (done) => { const user = new Parse.User(); reconfigureServer({ @@ -409,12 +465,12 @@ describe("Password Policy: ", () => { fail("should login with new password"); done(); }); - }).catch((error)=> { + }).catch((error) => { jfail(error); fail("Failed to POST request password reset"); done(); }); - }).catch((error)=> { + }).catch((error) => { jfail(error); fail("Failed to get the reset link"); done(); @@ -839,7 +895,7 @@ describe("Password Policy: ", () => { reconfigureServer({ appName: 'passwordPolicy', passwordPolicy: { - maxPasswordAge: 0.5/(24*60*60) // 0.5 sec + maxPasswordAge: 0.5 / (24 * 60 * 60) // 0.5 sec }, publicServerURL: "http://localhost:8378/1" }).then(() => { @@ -880,7 +936,7 @@ describe("Password Policy: ", () => { reconfigureServer({ appName: 'passwordPolicy', passwordPolicy: { - maxPasswordAge: 0.5/(24*60*60) // 0.5 sec + maxPasswordAge: 0.5 / (24 * 60 * 60) // 0.5 sec }, publicServerURL: "http://localhost:8378/1" }).then(() => { @@ -979,7 +1035,7 @@ describe("Password Policy: ", () => { appName: 'passwordPolicy', emailAdapter: emailAdapter, passwordPolicy: { - maxPasswordAge: 0.5/(24*60*60) // 0.5 sec + maxPasswordAge: 0.5 / (24 * 60 * 60) // 0.5 sec }, publicServerURL: "http://localhost:8378/1" }).then(() => { diff --git a/src/Config.js b/src/Config.js index ed0b51248b..01b15303f4 100644 --- a/src/Config.js +++ b/src/Config.js @@ -127,10 +127,16 @@ export class Config { throw 'passwordPolicy.resetTokenValidityDuration must be a positive number'; } - if(passwordPolicy.validatorPattern && !(passwordPolicy.validatorPattern instanceof RegExp)) { - throw 'passwordPolicy.validatorPattern must be a RegExp.'; + if(passwordPolicy.validatorPattern){ + if(typeof(passwordPolicy.validatorPattern) === 'string') { + passwordPolicy.validatorPattern = new RegExp(passwordPolicy.validatorPattern); + } + else if(!(passwordPolicy.validatorPattern instanceof RegExp)){ + throw 'passwordPolicy.validatorPattern must be a regex string or RegExp object.'; + } } + if(passwordPolicy.validatorCallback && typeof passwordPolicy.validatorCallback !== 'function') { throw 'passwordPolicy.validatorCallback must be a function.'; }