Adds password expiry support to password policy (#3068)

* Adding support for password expiry policy

* Renamed daysBeforeExpiry -> maxPasswordAge
This commit is contained in:
Bhaskar Reddy Yasa
2016-11-21 21:16:38 +05:30
committed by Diwakar Cherukumilli
parent 11c6170ed1
commit edb7b70ced
8 changed files with 397 additions and 89 deletions

View File

@@ -15,7 +15,7 @@ describe("Password Policy: ", () => {
},
sendMail: () => {
}
}
};
reconfigureServer({
appName: 'passwordPolicy',
emailAdapter: emailAdapter,
@@ -29,7 +29,11 @@ describe("Password Policy: ", () => {
user.set('email', 'user@parse.com');
return user.signUp();
}).then(user => {
Parse.User.requestPasswordReset("user@parse.com");
Parse.User.requestPasswordReset('user@parse.com').catch((err) => {
jfail(err);
fail("Reset password request should not fail");
done();
});
}).then(() => {
// wait for a bit more than the validity duration set
setTimeout(() => {
@@ -64,7 +68,7 @@ describe("Password Policy: ", () => {
},
sendMail: () => {
}
}
};
reconfigureServer({
appName: 'passwordPolicy',
emailAdapter: emailAdapter,
@@ -78,7 +82,11 @@ describe("Password Policy: ", () => {
user.set('email', 'user@parse.com');
return user.signUp();
}).then(user => {
Parse.User.requestPasswordReset("user@parse.com");
Parse.User.requestPasswordReset('user@parse.com').catch((err) => {
jfail(err);
fail("Reset password request should not fail");
done();
});
}).then(() => {
// wait for a bit but less than the validity duration
setTimeout(() => {
@@ -190,7 +198,7 @@ describe("Password Policy: ", () => {
})
});
it('signup should succeed if password confirms to the policy enforced using validatorPattern', (done) => {
it('signup should succeed if password conforms to the policy enforced using validatorPattern', (done) => {
const user = new Parse.User();
reconfigureServer({
appName: 'passwordPolicy',
@@ -203,16 +211,22 @@ describe("Password Policy: ", () => {
user.setPassword("1digit");
user.set('email', 'user1@parse.com');
user.signUp().then(() => {
Parse.User.logOut();
Parse.User.logIn("user1", "1digit").then(function (user) {
done();
}).catch((err) => {
jfail(err);
fail("Should be able to login");
Parse.User.logOut().then(() => {
Parse.User.logIn("user1", "1digit").then(function (user) {
done();
}).catch((err) => {
jfail(err);
fail("Should be able to login");
done();
});
}).catch((error) => {
jfail(error);
fail('logout should have succeeded');
done();
});
}).catch((error) => {
fail('Should have succeeded as password confirms to the policy.');
jfail(error);
fail('Signup should have succeeded as password conforms to the policy.');
done();
});
})
@@ -240,7 +254,7 @@ describe("Password Policy: ", () => {
})
});
it('signup should succeed if password confirms to the policy enforced using validatorCallback', (done) => {
it('signup should succeed if password conforms to the policy enforced using validatorCallback', (done) => {
const user = new Parse.User();
reconfigureServer({
appName: 'passwordPolicy',
@@ -253,16 +267,22 @@ describe("Password Policy: ", () => {
user.setPassword("oneUpper");
user.set('email', 'user1@parse.com');
user.signUp().then(() => {
Parse.User.logOut();
Parse.User.logIn("user1", "oneUpper").then(function (user) {
done();
}).catch((err) => {
jfail(err);
fail("Should be able to login");
Parse.User.logOut().then(() => {
Parse.User.logIn("user1", "oneUpper").then(function (user) {
done();
}).catch((err) => {
jfail(err);
fail("Should be able to login");
done();
});
}).catch(error => {
jfail(error);
fail("Logout should have succeeded");
done();
});
}).catch((error) => {
fail('Should have succeeded as password confirms to the policy.');
jfail(error);
fail('Should have succeeded as password conforms to the policy.');
done();
});
})
@@ -291,7 +311,7 @@ describe("Password Policy: ", () => {
})
});
it('signup should fail if password does confirms to validatorPattern but fails validatorCallback', (done) => {
it('signup should fail if password matches validatorPattern but fails validatorCallback', (done) => {
const user = new Parse.User();
reconfigureServer({
appName: 'passwordPolicy',
@@ -314,7 +334,7 @@ describe("Password Policy: ", () => {
})
});
it('signup should succeed if password confirms to both validatorPattern and validatorCallback', (done) => {
it('signup should succeed if password conforms to both validatorPattern and validatorCallback', (done) => {
const user = new Parse.User();
reconfigureServer({
appName: 'passwordPolicy',
@@ -328,24 +348,30 @@ describe("Password Policy: ", () => {
user.setPassword("oneUpper");
user.set('email', 'user1@parse.com');
user.signUp().then(() => {
Parse.User.logOut();
Parse.User.logIn("user1", "oneUpper").then(function (user) {
done();
}).catch((err) => {
jfail(err);
fail("Should be able to login");
Parse.User.logOut().then(() => {
Parse.User.logIn("user1", "oneUpper").then(function (user) {
done();
}).catch((err) => {
jfail(err);
fail("Should be able to login");
done();
});
}).catch(error => {
jfail(error);
fail("logout should have succeeded");
done();
});
}).catch((error) => {
fail('Should have succeeded as password confirms to the policy.');
jfail(error);
fail('Should have succeeded as password conforms to the policy.');
done();
});
})
});
it('should reset password if new password confirms to password policy', done => {
var user = new Parse.User();
var emailAdapter = {
it('should reset password if new password conforms to password policy', done => {
const user = new Parse.User();
const emailAdapter = {
sendVerificationEmail: () => Promise.resolve(),
sendPasswordResetEmail: options => {
requestp.get({
@@ -355,14 +381,14 @@ describe("Password Policy: ", () => {
resolveWithFullResponse: true
}).then((response) => {
expect(response.statusCode).toEqual(302);
var re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&username=user1/;
var match = response.body.match(re);
const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&username=user1/;
const match = response.body.match(re);
if (!match) {
fail("should have a token");
done();
return;
}
var token = match[1];
const token = match[1];
requestp.post({
uri: "http://localhost:8378/1/apps/test/request_password_reset",
@@ -397,7 +423,7 @@ describe("Password Policy: ", () => {
},
sendMail: () => {
}
}
};
reconfigureServer({
appName: 'passwordPolicy',
verifyUserEmails: false,
@@ -411,12 +437,10 @@ describe("Password Policy: ", () => {
user.setPassword("has 1 digit");
user.set('email', 'user1@parse.com');
user.signUp().then(() => {
Parse.User.requestPasswordReset('user1@parse.com', {
error: (err) => {
jfail(err);
fail("Reset password request should not fail");
done();
}
Parse.User.requestPasswordReset('user1@parse.com').catch((err) => {
jfail(err);
fail("Reset password request should not fail");
done();
});
}).catch(error => {
jfail(error);
@@ -427,8 +451,8 @@ describe("Password Policy: ", () => {
});
it('should fail to reset password if the new password does not conform to password policy', done => {
var user = new Parse.User();
var emailAdapter = {
const user = new Parse.User();
const emailAdapter = {
sendVerificationEmail: () => Promise.resolve(),
sendPasswordResetEmail: options => {
requestp.get({
@@ -438,14 +462,14 @@ describe("Password Policy: ", () => {
resolveWithFullResponse: true
}).then((response) => {
expect(response.statusCode).toEqual(302);
var re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&username=user1/;
var match = response.body.match(re);
const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&username=user1/;
const match = response.body.match(re);
if (!match) {
fail("should have a token");
done();
return;
}
var token = match[1];
const token = match[1];
requestp.post({
uri: "http://localhost:8378/1/apps/test/request_password_reset",
@@ -458,7 +482,7 @@ describe("Password Policy: ", () => {
resolveWithFullResponse: true
}).then((response) => {
expect(response.statusCode).toEqual(302);
expect(response.body).toEqual(`Found. Redirecting to http://localhost:8378/1/apps/choose_password?username=user1&token=${token}&id=test&error=Password%20does%20not%20confirm%20to%20the%20Password%20Policy.&app=passwordPolicy`);
expect(response.body).toEqual(`Found. Redirecting to http://localhost:8378/1/apps/choose_password?username=user1&token=${token}&id=test&error=Password%20does%20not%20meet%20the%20Password%20Policy%20requirements.&app=passwordPolicy`);
Parse.User.logIn("user1", "has 1 digit").then(function (user) {
done();
@@ -480,7 +504,7 @@ describe("Password Policy: ", () => {
},
sendMail: () => {
}
}
};
reconfigureServer({
appName: 'passwordPolicy',
verifyUserEmails: false,
@@ -494,12 +518,10 @@ describe("Password Policy: ", () => {
user.setPassword("has 1 digit");
user.set('email', 'user1@parse.com');
user.signUp().then(() => {
Parse.User.requestPasswordReset('user1@parse.com', {
error: (err) => {
jfail(err);
fail("Reset password request should not fail");
done();
}
Parse.User.requestPasswordReset('user1@parse.com').catch((err) => {
jfail(err);
fail("Reset password request should not fail");
done();
});
}).catch(error => {
jfail(error);
@@ -591,8 +613,8 @@ describe("Password Policy: ", () => {
});
it('should fail to reset password if the new password contains username and not allowed by password policy', done => {
var user = new Parse.User();
var emailAdapter = {
const user = new Parse.User();
const emailAdapter = {
sendVerificationEmail: () => Promise.resolve(),
sendPasswordResetEmail: options => {
requestp.get({
@@ -602,14 +624,14 @@ describe("Password Policy: ", () => {
resolveWithFullResponse: true
}).then((response) => {
expect(response.statusCode).toEqual(302);
var re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&username=user1/;
var match = response.body.match(re);
const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&username=user1/;
const match = response.body.match(re);
if (!match) {
fail("should have a token");
done();
return;
}
var token = match[1];
const token = match[1];
requestp.post({
uri: "http://localhost:8378/1/apps/test/request_password_reset",
@@ -622,7 +644,7 @@ describe("Password Policy: ", () => {
resolveWithFullResponse: true
}).then((response) => {
expect(response.statusCode).toEqual(302);
expect(response.body).toEqual(`Found. Redirecting to http://localhost:8378/1/apps/choose_password?username=user1&token=${token}&id=test&error=Password%20does%20not%20confirm%20to%20the%20Password%20Policy.&app=passwordPolicy`);
expect(response.body).toEqual(`Found. Redirecting to http://localhost:8378/1/apps/choose_password?username=user1&token=${token}&id=test&error=Password%20does%20not%20meet%20the%20Password%20Policy%20requirements.&app=passwordPolicy`);
Parse.User.logIn("user1", "r@nd0m").then(function (user) {
done();
@@ -645,7 +667,7 @@ describe("Password Policy: ", () => {
},
sendMail: () => {
}
}
};
reconfigureServer({
appName: 'passwordPolicy',
verifyUserEmails: false,
@@ -659,12 +681,10 @@ describe("Password Policy: ", () => {
user.setPassword("r@nd0m");
user.set('email', 'user1@parse.com');
user.signUp().then(() => {
Parse.User.requestPasswordReset('user1@parse.com', {
error: (err) => {
jfail(err);
fail("Reset password request should not fail");
done();
}
Parse.User.requestPasswordReset('user1@parse.com').catch((err) => {
jfail(err);
fail("Reset password request should not fail");
done();
});
}).catch(error => {
jfail(error);
@@ -675,8 +695,8 @@ describe("Password Policy: ", () => {
});
it('should reset password even if the new password contains user name while the policy allows', done => {
var user = new Parse.User();
var emailAdapter = {
const user = new Parse.User();
const emailAdapter = {
sendVerificationEmail: () => Promise.resolve(),
sendPasswordResetEmail: options => {
requestp.get({
@@ -686,14 +706,14 @@ describe("Password Policy: ", () => {
resolveWithFullResponse: true
}).then(response => {
expect(response.statusCode).toEqual(302);
var re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&username=user1/;
var match = response.body.match(re);
const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&username=user1/;
const match = response.body.match(re);
if (!match) {
fail("should have a token");
done();
return;
}
var token = match[1];
const token = match[1];
requestp.post({
uri: "http://localhost:8378/1/apps/test/request_password_reset",
@@ -727,7 +747,7 @@ describe("Password Policy: ", () => {
},
sendMail: () => {
}
}
};
reconfigureServer({
appName: 'passwordPolicy',
verifyUserEmails: false,
@@ -742,16 +762,250 @@ describe("Password Policy: ", () => {
user.setPassword("has 1 digit");
user.set('email', 'user1@parse.com');
user.signUp().then(() => {
Parse.User.requestPasswordReset('user1@parse.com', {
error: (err) => {
jfail(err);
fail("Reset password request should not fail");
done();
}
Parse.User.requestPasswordReset('user1@parse.com').catch((err) => {
jfail(err);
fail("Reset password request should not fail");
done();
});
}).catch(error => {
});
}).catch(error => {
jfail(error);
fail("signUp should not fail");
done();
});
});
it('should fail if passwordPolicy.maxPasswordAge is not a number', done => {
reconfigureServer({
appName: 'passwordPolicy',
passwordPolicy: {
maxPasswordAge: "not a number"
},
publicServerURL: "http://localhost:8378/1"
}).then(() => {
fail('passwordPolicy.maxPasswordAge "not a number" test failed');
done();
}).catch(err => {
expect(err).toEqual('passwordPolicy.maxPasswordAge must be a positive number');
done();
});
});
it('should fail if passwordPolicy.maxPasswordAge is a negative number', done => {
reconfigureServer({
appName: 'passwordPolicy',
passwordPolicy: {
maxPasswordAge: -100
},
publicServerURL: "http://localhost:8378/1"
}).then(() => {
fail('passwordPolicy.maxPasswordAge negative number test failed');
done();
}).catch(err => {
expect(err).toEqual('passwordPolicy.maxPasswordAge must be a positive number');
done();
});
});
it('should succeed if logged in before password expires', (done) => {
const user = new Parse.User();
reconfigureServer({
appName: 'passwordPolicy',
passwordPolicy: {
maxPasswordAge: 1 // 1 day
},
publicServerURL: "http://localhost:8378/1"
}).then(() => {
user.setUsername("user1");
user.setPassword("user1");
user.set('email', 'user1@parse.com');
user.signUp().then((u) => {
Parse.User.logIn("user1", "user1").then((user) => {
done();
}).catch((error) => {
jfail(error);
fail('Login should have succeeded before password expiry.');
done();
});
}).catch((error) => {
jfail(error);
fail("signUp should not fail");
fail('Signup failed.');
done();
});
})
});
it('should fail if logged in after password expires', (done) => {
const user = new Parse.User();
reconfigureServer({
appName: 'passwordPolicy',
passwordPolicy: {
maxPasswordAge: 0.5/(24*60*60) // 0.5 sec
},
publicServerURL: "http://localhost:8378/1"
}).then(() => {
user.setUsername("user1");
user.setPassword("user1");
user.set('email', 'user1@parse.com');
user.signUp().then(() => {
// wait for a bit more than the validity duration set
setTimeout(() => {
Parse.User.logIn("user1", "user1").then(() => {
fail("logIn should have failed");
done();
}).catch((error) => {
expect(error.code).toEqual(Parse.Error.OBJECT_NOT_FOUND);
expect(error.message).toEqual('Your password has expired. Please reset your password.');
done();
});
}, 1000);
}).catch((error) => {
jfail(error);
fail('Signup failed.');
done();
});
});
});
it('should apply password expiry policy to existing user upon first login after policy is enabled', (done) => {
const user = new Parse.User();
reconfigureServer({
appName: 'passwordPolicy',
publicServerURL: "http://localhost:8378/1"
}).then(() => {
user.setUsername("user1");
user.setPassword("user1");
user.set('email', 'user1@parse.com');
user.signUp().then(() => {
Parse.User.logOut().then(() => {
reconfigureServer({
appName: 'passwordPolicy',
passwordPolicy: {
maxPasswordAge: 0.5/(24*60*60) // 0.5 sec
},
publicServerURL: "http://localhost:8378/1"
}).then(() => {
Parse.User.logIn("user1", "user1").then((u) => {
Parse.User.logOut().then(() => {
// wait for a bit more than the validity duration set
setTimeout(() => {
Parse.User.logIn("user1", "user1").then(() => {
fail("logIn should have failed");
done();
}).catch((error) => {
expect(error.code).toEqual(Parse.Error.OBJECT_NOT_FOUND);
expect(error.message).toEqual('Your password has expired. Please reset your password.');
done();
});
}, 2000);
}).catch(error => {
jfail(error);
fail("logout should have succeeded");
done();
});
}).catch((error) => {
jfail(error);
fail('Login failed.');
done();
});
});
}).catch(error => {
jfail(error);
fail("logout should have succeeded");
done();
});
}).catch((error) => {
jfail(error);
fail('Signup failed.');
done();
});
});
});
it('should reset password timestamp when password is reset', done => {
const user = new Parse.User();
const emailAdapter = {
sendVerificationEmail: () => Promise.resolve(),
sendPasswordResetEmail: options => {
requestp.get({
uri: options.link,
followRedirect: false,
simple: false,
resolveWithFullResponse: true
}).then(response => {
expect(response.statusCode).toEqual(302);
const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&username=user1/;
const match = response.body.match(re);
if (!match) {
fail("should have a token");
done();
return;
}
const token = match[1];
requestp.post({
uri: "http://localhost:8378/1/apps/test/request_password_reset",
body: `new_password=uuser11&token=${token}&username=user1`,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
followRedirect: false,
simple: false,
resolveWithFullResponse: true
}).then(response => {
expect(response.statusCode).toEqual(302);
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/password_reset_success.html?username=user1');
Parse.User.logIn("user1", "uuser11").then(function (user) {
done();
}).catch(err => {
jfail(err);
fail("should login with new password");
done();
});
}).catch(error => {
jfail(error);
fail("Failed to POST request password reset");
});
}).catch(error => {
jfail(error);
fail("Failed to get the reset link");
});
},
sendMail: () => {
}
};
reconfigureServer({
appName: 'passwordPolicy',
emailAdapter: emailAdapter,
passwordPolicy: {
maxPasswordAge: 0.5/(24*60*60) // 0.5 sec
},
publicServerURL: "http://localhost:8378/1"
}).then(() => {
user.setUsername("user1");
user.setPassword("user1");
user.set('email', 'user1@parse.com');
user.signUp().then(() => {
// wait for a bit more than the validity duration set
setTimeout(() => {
Parse.User.logIn("user1", "user1").then(() => {
fail("logIn should have failed");
done();
}).catch((error) => {
expect(error.code).toEqual(Parse.Error.OBJECT_NOT_FOUND);
expect(error.message).toEqual('Your password has expired. Please reset your password.');
Parse.User.requestPasswordReset('user1@parse.com').catch((err) => {
jfail(err);
fail("Reset password request should not fail");
done();
});
});
}, 1000);
}).catch((error) => {
jfail(error);
fail('Signup failed.');
done();
});
});