Files
kami-parse-server/spec/VerifyUserPassword.spec.js
Johnny 2ef437a2bc Added verify password to users router and tests. (#4747)
* Added verify password to users router and tests.

* Added more tests to support more coverage.

* Added additional tests to spec. Removed condition from verifyPassword function where authData null keys condition wasn't necessary.

* Removed POST handling from verifyPassword.

* Refactored handleLogin and handleVerifyPassword to use shared helper function to validate the password provided in the request.

* Refactored verifyPassword and login to not use try/catch. Parent promise returns the error. Moved login specific functions to login handler.

* Added account lockout policy to verify password function. Added test spec for account lockout in verify password.

* no message

* Merged new changes from master. Made changes as requested from comments.

* We cannot remove hidden properties from the helper before returning to the login function. The password expiration check in the login function is dependent on some hidden properties, otherwise three password policy tests fail.
2018-06-13 14:19:53 -04:00

495 lines
16 KiB
JavaScript

"use strict";
const rp = require('request-promise');
const MockEmailAdapterWithOptions = require('./MockEmailAdapterWithOptions');
const verifyPassword = function (login, password, isEmail = false) {
const body = (!isEmail) ? { username: login, password } : { email: login, password };
return rp.get({
url: Parse.serverURL + '/verifyPassword',
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-REST-API-Key': 'rest'
},
body,
json: true
}).then((res) => res)
.catch((err) => err);
};
const isAccountLockoutError = function (username, password, duration, waitTime) {
return new Promise((resolve, reject) => {
setTimeout(() => {
Parse.User.logIn(username, password)
.then(() => reject('login should have failed'))
.catch(err => {
if (err.message === 'Your account is locked due to multiple failed login attempts. Please try again after ' + duration + ' minute(s)') {
resolve();
} else {
reject(err);
}
});
}, waitTime);
});
};
describe("Verify User Password", () => {
it('fails to verify password when masterKey has locked out user', (done) => {
const user = new Parse.User();
const ACL = new Parse.ACL();
ACL.setPublicReadAccess(false);
ACL.setPublicWriteAccess(false);
user.setUsername('testuser');
user.setPassword('mypass');
user.setACL(ACL);
user.signUp().then(() => {
return Parse.User.logIn('testuser', 'mypass');
}).then((user) => {
equal(user.get('username'), 'testuser');
// Lock the user down
const ACL = new Parse.ACL();
user.setACL(ACL);
return user.save(null, { useMasterKey: true });
}).then(() => {
expect(user.getACL().getPublicReadAccess()).toBe(false);
return rp.get({
url: Parse.serverURL + '/verifyPassword',
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-REST-API-Key': 'rest'
},
qs: {
username: 'testuser',
password: 'mypass',
}
});
}).then((res) => {
fail(res);
done();
}).catch((err) => {
expect(err.statusCode).toBe(404);
expect(err.error).toMatch('{"code":101,"error":"Invalid username/password."}');
done();
});
});
it('fails to verify password when username is not provided in query string REST API', (done) => {
const user = new Parse.User();
user.save({
username: 'testuser',
password: 'mypass',
email: 'my@user.com'
}).then(() => {
return rp.get({
url: Parse.serverURL + '/verifyPassword',
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-REST-API-Key': 'rest'
},
qs: {
username: '',
password: 'mypass',
}
});
}).then((res) => {
fail(res);
done();
}).catch((err) => {
expect(err.statusCode).toBe(400);
expect(err.error).toMatch('{"code":200,"error":"username/email is required."}');
done();
});
});
it('fails to verify password when email is not provided in query string REST API', (done) => {
const user = new Parse.User();
user.save({
username: 'testuser',
password: 'mypass',
email: 'my@user.com'
}).then(() => {
return rp.get({
url: Parse.serverURL + '/verifyPassword',
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-REST-API-Key': 'rest'
},
qs: {
email: '',
password: 'mypass',
}
});
}).then((res) => {
fail(res);
done();
}).catch((err) => {
expect(err.statusCode).toBe(400);
expect(err.error).toMatch('{"code":200,"error":"username/email is required."}');
done();
});
});
it('fails to verify password when username is not provided with json payload REST API', (done) => {
const user = new Parse.User();
user.save({
username: 'testuser',
password: 'mypass',
email: 'my@user.com'
}).then(() => {
return verifyPassword('', 'mypass');
}).then((res) => {
expect(res.statusCode).toBe(400);
expect(JSON.stringify(res.error)).toMatch('{"code":200,"error":"username/email is required."}');
done();
}).catch((err) => {
fail(err);
done();
});
});
it('fails to verify password when email is not provided with json payload REST API', (done) => {
const user = new Parse.User();
user.save({
username: 'testuser',
password: 'mypass',
email: 'my@user.com'
}).then(() => {
return verifyPassword('', 'mypass', true);
}).then((res) => {
expect(res.statusCode).toBe(400);
expect(JSON.stringify(res.error)).toMatch('{"code":200,"error":"username/email is required."}');
done();
}).catch((err) => {
fail(err);
done();
});
});
it('fails to verify password when password is not provided with json payload REST API', (done) => {
const user = new Parse.User();
user.save({
username: 'testuser',
password: 'mypass',
email: 'my@user.com'
}).then(() => {
return verifyPassword('testuser', '');
}).then((res) => {
expect(res.statusCode).toBe(400);
expect(JSON.stringify(res.error)).toMatch('{"code":201,"error":"password is required."}');
done();
}).catch((err) => {
fail(err);
done();
});
});
it('fails to verify password when username matches but password does not match hash with json payload REST API', (done) => {
const user = new Parse.User();
user.save({
username: 'testuser',
password: 'mypass',
email: 'my@user.com'
}).then(() => {
return verifyPassword('testuser', 'wrong password');
}).then((res) => {
expect(res.statusCode).toBe(404);
expect(JSON.stringify(res.error)).toMatch('{"code":101,"error":"Invalid username/password."}');
done();
}).catch((err) => {
fail(err);
done();
});
});
it('fails to verify password when email matches but password does not match hash with json payload REST API', (done) => {
const user = new Parse.User();
user.save({
username: 'testuser',
password: 'mypass',
email: 'my@user.com'
}).then(() => {
return verifyPassword('my@user.com', 'wrong password', true);
}).then((res) => {
expect(res.statusCode).toBe(404);
expect(JSON.stringify(res.error)).toMatch('{"code":101,"error":"Invalid username/password."}');
done();
}).catch((err) => {
fail(err);
done();
});
});
it('fails to verify password when typeof username does not equal string REST API', (done) => {
const user = new Parse.User();
user.save({
username: 'testuser',
password: 'mypass',
email: 'my@user.com'
}).then(() => {
return verifyPassword(123, 'mypass');
}).then((res) => {
expect(res.statusCode).toBe(404);
expect(JSON.stringify(res.error)).toMatch('{"code":101,"error":"Invalid username/password."}');
done();
}).catch((err) => {
fail(err);
done();
});
});
it('fails to verify password when typeof email does not equal string REST API', (done) => {
const user = new Parse.User();
user.save({
username: 'testuser',
password: 'mypass',
email: 'my@user.com'
}).then(() => {
return verifyPassword(123, 'mypass', true);
}).then((res) => {
expect(res.statusCode).toBe(404);
expect(JSON.stringify(res.error)).toMatch('{"code":101,"error":"Invalid username/password."}');
done();
}).catch((err) => {
fail(err);
done();
});
});
it('fails to verify password when typeof password does not equal string REST API', (done) => {
const user = new Parse.User();
user.save({
username: 'testuser',
password: 'mypass',
email: 'my@user.com'
}).then(() => {
return verifyPassword('my@user.com', 123, true);
}).then((res) => {
expect(res.statusCode).toBe(404);
expect(JSON.stringify(res.error)).toMatch('{"code":101,"error":"Invalid username/password."}');
done();
}).catch((err) => {
fail(err);
done();
});
});
it('fails to verify password when username cannot be found REST API', (done) => {
verifyPassword('mytestuser', 'mypass')
.then((res) => {
expect(res.statusCode).toBe(404);
expect(JSON.stringify(res.error)).toMatch('{"code":101,"error":"Invalid username/password."}');
done();
}).catch((err) => {
fail(err);
done();
});
});
it('fails to verify password when email cannot be found REST API', (done) => {
verifyPassword('my@user.com', 'mypass', true)
.then((res) => {
expect(res.statusCode).toBe(404);
expect(JSON.stringify(res.error)).toMatch('{"code":101,"error":"Invalid username/password."}');
done();
}).catch((err) => {
fail(err);
done();
});
});
it('fails to verify password when preventLoginWithUnverifiedEmail is set to true REST API', (done) => {
reconfigureServer({
publicServerURL: "http://localhost:8378/",
appName: 'emailVerify',
verifyUserEmails: true,
preventLoginWithUnverifiedEmail: true,
emailAdapter: MockEmailAdapterWithOptions({
fromAddress: 'parse@example.com',
apiKey: 'k',
domain: 'd',
}),
}).then(() => {
const user = new Parse.User();
return user.save({
username: 'unverified-user',
password: 'mypass',
email: 'unverified-email@user.com'
});
}).then(() => {
return verifyPassword('unverified-email@user.com', 'mypass', true);
}).then((res) => {
expect(res.statusCode).toBe(400);
expect(JSON.stringify(res.error)).toMatch('{"code":205,"error":"User email is not verified."}');
done();
}).catch((err) => {
fail(err);
done();
});
});
it('verify password lock account if failed verify password attempts are above threshold', done => {
reconfigureServer({
appName: 'lockout threshold',
accountLockout: {
duration: 1,
threshold: 2
},
publicServerURL: "http://localhost:8378/"
})
.then(() => {
const user = new Parse.User();
return user.save({
username: 'testuser',
password: 'mypass',
email: 'my@user.com'
})
})
.then(() => {
return verifyPassword('testuser', 'wrong password');
})
.then(() => {
return verifyPassword('testuser', 'wrong password');
})
.then(() => {
return verifyPassword('testuser', 'wrong password');
})
.then(() => {
return isAccountLockoutError('testuser', 'wrong password', 1, 1);
})
.then(() => {
done();
})
.catch(err => {
fail('lock account after failed login attempts test failed: ' + JSON.stringify(err));
done();
});
});
it('succeed in verifying password when username and email are provided and password matches hash with json payload REST API', (done) => {
const user = new Parse.User();
user.save({
username: 'testuser',
password: 'mypass',
email: 'my@user.com'
}).then(() => {
return rp.get({
url: Parse.serverURL + '/verifyPassword',
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-REST-API-Key': 'rest'
},
body: {
username: 'testuser',
email: 'my@user.com',
password: 'mypass'
},
json: true
}).then((res) => res)
.catch((err) => err);
}).then((res) => {
expect(typeof res).toBe('object');
expect(typeof res['objectId']).toEqual('string');
expect(res.hasOwnProperty('sessionToken')).toEqual(false);
expect(res.hasOwnProperty('password')).toEqual(false);
done();
}).catch((err) => {
fail(err);
done();
});
});
it('succeed in verifying password when username and password matches hash with json payload REST API', (done) => {
const user = new Parse.User();
user.save({
username: 'testuser',
password: 'mypass',
email: 'my@user.com'
}).then(() => {
return verifyPassword('testuser', 'mypass');
}).then((res) => {
expect(typeof res).toBe('object');
expect(typeof res['objectId']).toEqual('string');
expect(res.hasOwnProperty('sessionToken')).toEqual(false);
expect(res.hasOwnProperty('password')).toEqual(false);
done();
});
});
it('succeed in verifying password when email and password matches hash with json payload REST API', (done) => {
const user = new Parse.User();
user.save({
username: 'testuser',
password: 'mypass',
email: 'my@user.com'
}).then(() => {
return verifyPassword('my@user.com', 'mypass', true);
}).then((res) => {
expect(typeof res).toBe('object');
expect(typeof res['objectId']).toEqual('string');
expect(res.hasOwnProperty('sessionToken')).toEqual(false);
expect(res.hasOwnProperty('password')).toEqual(false);
done();
});
});
it('succeed to verify password when username and password provided in query string REST API', (done) => {
const user = new Parse.User();
user.save({
username: 'testuser',
password: 'mypass',
email: 'my@user.com'
}).then(() => {
return rp.get({
url: Parse.serverURL + '/verifyPassword',
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-REST-API-Key': 'rest'
},
qs: {
username: 'testuser',
password: 'mypass',
}
});
}).then((res) => {
expect(typeof res).toBe('string');
const body = JSON.parse(res);
expect(typeof body['objectId']).toEqual('string');
expect(body.hasOwnProperty('sessionToken')).toEqual(false);
expect(body.hasOwnProperty('password')).toEqual(false);
done();
});
});
it('succeed to verify password when email and password provided in query string REST API', (done) => {
const user = new Parse.User();
user.save({
username: 'testuser',
password: 'mypass',
email: 'my@user.com'
}).then(() => {
return rp.get({
url: Parse.serverURL + '/verifyPassword',
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-REST-API-Key': 'rest'
},
qs: {
email: 'my@user.com',
password: 'mypass',
}
});
}).then((res) => {
expect(typeof res).toBe('string');
const body = JSON.parse(res);
expect(typeof body['objectId']).toEqual('string');
expect(body.hasOwnProperty('sessionToken')).toEqual(false);
expect(body.hasOwnProperty('password')).toEqual(false);
done();
});
});
it('succeed to verify password with username when user1 has username === user2 email REST API', (done) => {
const user1 = new Parse.User();
user1.save({
username: 'email@user.com',
password: 'mypass1',
email: '1@user.com'
}).then(() => {
const user2 = new Parse.User();
return user2.save({
username: 'user2',
password: 'mypass2',
email: 'email@user.com'
});
}).then(() => {
return verifyPassword('email@user.com', 'mypass1');
}).then((res) => {
expect(typeof res).toBe('object');
expect(typeof res['objectId']).toEqual('string');
expect(res.hasOwnProperty('sessionToken')).toEqual(false);
expect(res.hasOwnProperty('password')).toEqual(false);
done();
});
});
})