-
Notifications
You must be signed in to change notification settings - Fork 78
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Standardise responses for invalid new passwords (#1411)
The fix is pushed deeper into the `util/crypto.js` code than where validation previously occurred. This means that validation only needs to be performed in a single place, rather than every point of use. This in turn means it should be harder to forget validation & edge cases in future use of `hashPassword()`. Closes #1407
- Loading branch information
Showing
9 changed files
with
53 additions
and
51 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,7 +8,7 @@ describe('api: /sessions', () => { | |
|
||
it('should return a new session if the information is valid', testService((service) => | ||
service.post('/v1/sessions') | ||
.send({ email: '[email protected]', password: 'chelsea' }) | ||
.send({ email: '[email protected]', password: 'password4chelsea' }) | ||
.expect(200) | ||
.then(({ body }) => { | ||
body.should.be.a.Session(); | ||
|
@@ -20,9 +20,9 @@ describe('api: /sessions', () => { | |
// and reject them before passing the values to bcrypt. | ||
describe('weird bcrypt implementation details', () => { | ||
[ | ||
[ 'repeated once', 'chelsea\0chelsea' ], // eslint-disable-line no-multi-spaces | ||
[ 'repeated twice', 'chelsea\0chelsea\0chelsea' ], // eslint-disable-line no-multi-spaces | ||
[ 'repeated until truncation', 'chelsea\0chelsea\0chelsea\0chelsea\0chelsea\0chelsea\0chelsea\0chelsea\0chelsea\0' ], | ||
[ 'repeated once', 'password4chelsea\0password4chelsea' ], // eslint-disable-line no-multi-spaces | ||
[ 'repeated twice', 'password4chelsea\0password4chelsea\0password4chelsea' ], // eslint-disable-line no-multi-spaces | ||
[ 'repeated until truncation', 'password4chelsea\0password4chelsea\0password4chelsea\0password4chelsea\0password4' ], | ||
].forEach(([ description, password ]) => { | ||
it(`should treat a password ${description} as the singular version of the same`, testService((service) => | ||
service.post('/v1/sessions') | ||
|
@@ -36,23 +36,23 @@ describe('api: /sessions', () => { | |
|
||
it('should treat email addresses case insensitively', testService((service) => | ||
service.post('/v1/sessions') | ||
.send({ email: '[email protected]', password: 'chelsea' }) | ||
.send({ email: '[email protected]', password: 'password4chelsea' }) | ||
.expect(200) | ||
.then(({ body }) => { | ||
body.should.be.a.Session(); | ||
}))); | ||
|
||
it('should provide a csrf token when the session returns', testService((service) => | ||
service.post('/v1/sessions') | ||
.send({ email: '[email protected]', password: 'chelsea' }) | ||
.send({ email: '[email protected]', password: 'password4chelsea' }) | ||
.expect(200) | ||
.then(({ body }) => { | ||
body.csrf.should.be.a.token(); | ||
}))); | ||
|
||
it('should set cookie information when the session returns', testService((service) => | ||
service.post('/v1/sessions') | ||
.send({ email: '[email protected]', password: 'chelsea' }) | ||
.send({ email: '[email protected]', password: 'password4chelsea' }) | ||
.expect(200) | ||
.then(({ body, headers }) => { | ||
// i don't know how this becomes an array but i think superagent does it. | ||
|
@@ -71,7 +71,7 @@ describe('api: /sessions', () => { | |
|
||
it('should log the action in the audit log', testService((service) => | ||
service.post('/v1/sessions') | ||
.send({ email: '[email protected]', password: 'alice' }) | ||
.send({ email: '[email protected]', password: 'password4alice' }) | ||
.set('User-Agent', 'central/tests') | ||
.expect(200) | ||
.then(({ body }) => body.token) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -133,11 +133,19 @@ describe('api: /users', () => { | |
.then(({ password }) => { should.not.exist(password); }) | ||
]))))); | ||
|
||
it('should not accept a password that is too short', testService((service) => | ||
service.login('alice', (asAlice) => | ||
asAlice.post('/v1/users') | ||
.send({ email: '[email protected]', password: 'short' }) | ||
.expect(400)))); | ||
[ | ||
[ 'too short', 'short' ], | ||
[ 'too long', 'loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong' ], // eslint-disable-line no-multi-spaces | ||
[ 'object', {} ], // eslint-disable-line no-multi-spaces | ||
[ 'array', [] ], // eslint-disable-line no-multi-spaces | ||
[ 'number', 123 ], // eslint-disable-line no-multi-spaces | ||
].forEach(([ description, password ]) => { | ||
it(`should not accept ${description} password`, testService((service) => | ||
service.login('alice', (asAlice) => | ||
asAlice.post('/v1/users') | ||
.send({ email: '[email protected]', password }) | ||
.expect(400)))); | ||
}); | ||
|
||
it('should send an email to provisioned users', testService((service) => | ||
service.login('alice', (asAlice) => | ||
|
@@ -350,7 +358,7 @@ describe('api: /users', () => { | |
email.subject.should.equal('ODK Central account password reset'); | ||
|
||
return service.post('/v1/sessions') | ||
.send({ email: '[email protected]', password: 'bob' }) | ||
.send({ email: '[email protected]', password: 'password4bob' }) | ||
.expect(401); | ||
})))); | ||
|
||
|
@@ -536,7 +544,7 @@ describe('api: /users', () => { | |
} else { | ||
after.body.email.should.equal('[email protected]'); | ||
return service.post('/v1/sessions') | ||
.send({ email: '[email protected]', password: 'bob' }) | ||
.send({ email: '[email protected]', password: 'password4bob' }) | ||
.expect(200); | ||
} | ||
}))))); | ||
|
@@ -623,7 +631,7 @@ describe('api: /users', () => { | |
asAlice.get('/v1/users/current') | ||
.expect(200) | ||
.then(({ body }) => asAlice.put(`/v1/users/${body.id}/password`) | ||
.send({ old: 'alice', new: 'newpassword' }) | ||
.send({ old: 'password4alice', new: 'newpassword' }) | ||
.expect(404))))); | ||
}); | ||
} else { | ||
|
@@ -634,13 +642,13 @@ describe('api: /users', () => { | |
.expect(200) | ||
.then(({ body }) => service.login('chelsea', (asChelsea) => | ||
asChelsea.put(`/v1/users/${body.id}/password`) | ||
.send({ old: 'alice', new: 'chelsea' }) | ||
.send({ old: 'password4alice', new: 'chelsea' }) | ||
.expect(403)))))); | ||
|
||
it('should reject if the user does not exist', testService((service) => | ||
service.login('alice', (asAlice) => | ||
asAlice.put('/v1/users/9999/password') | ||
.send({ old: 'alice', new: 'chelsea' }) | ||
.send({ old: 'password4alice', new: 'password4chelsea' }) | ||
.expect(404)))); | ||
|
||
it('should reject if the old password is not correct', testService((service) => | ||
|
@@ -656,7 +664,7 @@ describe('api: /users', () => { | |
asAlice.get('/v1/users/current') | ||
.expect(200) | ||
.then(({ body }) => asAlice.put(`/v1/users/${body.id}/password`) | ||
.send({ old: 'alice', new: 'newpassword' }) | ||
.send({ old: 'password4alice', new: 'newpassword' }) | ||
.expect(200)) | ||
.then(({ body }) => { | ||
body.success.should.equal(true); | ||
|
@@ -670,14 +678,14 @@ describe('api: /users', () => { | |
asAlice.get('/v1/users/current') | ||
.expect(200) | ||
.then(({ body }) => asAlice.put(`/v1/users/${body.id}/password`) | ||
.send({ old: 'alice', new: '123456789' }) | ||
.send({ old: 'password4alice', new: '123456789' }) | ||
.expect(400))))); // 400.21 | ||
|
||
it('should allow nonadministrator users to set their own password', testService((service) => | ||
service.login('chelsea', (asChelsea) => | ||
asChelsea.get('/v1/users/current').expect(200).then(({ body }) => body.id) | ||
.then((chelseaId) => asChelsea.put(`/v1/users/${chelseaId}/password`) | ||
.send({ old: 'chelsea', new: 'newchelsea' }) | ||
.send({ old: 'password4chelsea', new: 'newchelsea' }) | ||
.expect(200) | ||
.then(() => service.post('/v1/sessions') | ||
.send({ email: '[email protected]', password: 'newchelsea' }) | ||
|
@@ -690,7 +698,7 @@ describe('api: /users', () => { | |
.expect(200); | ||
await anotherAlice.get('/v1/users/current').expect(200); | ||
await asAlice.put(`/v1/users/${id}/password`) | ||
.send({ old: 'alice', new: 'newpassword' }) | ||
.send({ old: 'password4alice', new: 'newpassword' }) | ||
.expect(200); | ||
// The other session has been deleted. | ||
await anotherAlice.get('/v1/users/current').expect(401); | ||
|
@@ -702,11 +710,11 @@ describe('api: /users', () => { | |
const asAlice = await service.login('alice'); | ||
const { body: { id } } = await asAlice.get('/v1/users/current') | ||
.expect(200); | ||
const basic = Buffer.from('[email protected]:alice').toString('base64'); | ||
const basic = Buffer.from('[email protected]:password4alice').toString('base64'); | ||
await service.put(`/v1/users/${id}/password`) | ||
.set('Authorization', `Basic ${basic}`) | ||
.set('X-Forwarded-Proto', 'https') | ||
.send({ old: 'alice', new: 'newpassword' }) | ||
.send({ old: 'password4alice', new: 'newpassword' }) | ||
.expect(200); | ||
await asAlice.get('/v1/users/current').expect(401); | ||
})); | ||
|
@@ -716,7 +724,7 @@ describe('api: /users', () => { | |
asAlice.get('/v1/users/current') | ||
.expect(200) | ||
.then(({ body }) => asAlice.put(`/v1/users/${body.id}/password`) | ||
.send({ old: 'alice', new: 'newpassword' }) | ||
.send({ old: 'password4alice', new: 'newpassword' }) | ||
.expect(200) | ||
.then(() => { | ||
const email = global.inbox.pop(); | ||
|
@@ -730,7 +738,7 @@ describe('api: /users', () => { | |
asAlice.get('/v1/users/current') | ||
.expect(200) | ||
.then(({ body }) => asAlice.put(`/v1/users/${body.id}/password`) | ||
.send({ old: 'alice', new: 'newpassword' }) | ||
.send({ old: 'password4alice', new: 'newpassword' }) | ||
.expect(200) | ||
.then(() => Promise.all([ | ||
Users.getByEmail('[email protected]').then((o) => o.get()), | ||
|
@@ -820,7 +828,7 @@ describe('api: /users', () => { | |
} | ||
} else { | ||
return service.post('/v1/sessions') | ||
.send({ email: '[email protected]', password: 'chelsea' }) | ||
.send({ email: '[email protected]', password: 'password4chelsea' }) | ||
.expect(401); | ||
} | ||
})))))); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,9 +7,9 @@ module.exports = ({ Assignments, Users, Projects }) => { | |
const proj = new Project({ name: 'Default Project' }); | ||
|
||
const users = [ | ||
new User({ email: '[email protected]', password: 'alice' }, { actor: new Actor({ type: 'user', displayName: 'Alice' }) }), | ||
new User({ email: '[email protected]', password: 'bob' }, { actor: new Actor({ type: 'user', displayName: 'Bob' }) }), | ||
new User({ email: '[email protected]', password: 'chelsea' }, { actor: new Actor({ type: 'user', displayName: 'Chelsea' }) }) | ||
new User({ email: '[email protected]', password: 'password4alice' }, { actor: new Actor({ type: 'user', displayName: 'Alice' }) }), | ||
new User({ email: '[email protected]', password: 'password4bob' }, { actor: new Actor({ type: 'user', displayName: 'Bob' }) }), | ||
new User({ email: '[email protected]', password: 'password4chelsea' }, { actor: new Actor({ type: 'user', displayName: 'Chelsea' }) }) | ||
]; | ||
|
||
// hash the passwords, create our three test users, then add grant Alice and Bob their rights. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,13 +5,13 @@ describe('basic authentication', () => { | |
it('should not accept email and password', testService((service) => | ||
service.get('/v1/users/current') | ||
.set('x-forwarded-proto', 'https') | ||
.auth('[email protected]', 'alice') | ||
.auth('[email protected]', 'password4alice') | ||
.expect(401))); | ||
} else { | ||
it('should accept email and password', testService((service) => | ||
service.get('/v1/users/current') | ||
.set('x-forwarded-proto', 'https') | ||
.auth('[email protected]', 'alice') | ||
.auth('[email protected]', 'password4alice') | ||
.expect(200))); | ||
} | ||
}); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -125,12 +125,12 @@ describe('preprocessors', () => { | |
)).should.be.rejectedWith(Problem, { problemCode: 401.2 })); | ||
|
||
it('should set the appropriate session if valid Basic auth credentials are given @slow', () => | ||
hashPassword('alice').then((hashed) => | ||
hashPassword('password4alice').then((hashed) => | ||
Promise.resolve(authHandler( | ||
{ Auth, Users: mockUsers('[email protected]', hashed) }, | ||
new Context( | ||
createRequest({ headers: { | ||
Authorization: `Basic ${Buffer.from('[email protected]:alice', 'utf8').toString('base64')}`, | ||
Authorization: `Basic ${Buffer.from('[email protected]:password4alice', 'utf8').toString('base64')}`, | ||
'X-Forwarded-Proto': 'https' | ||
} }), | ||
{ fieldKey: Option.none() } | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters