mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-10 23:36:14 -05:00
Improved error messaging for password reset process
refs #11878 - When password reset link is invalid previous messaging left the user without clear information about why the reset failed and what they could do about it. - Updated messaging around password reset tokens including detection of when password token has invalid structure, has expired or has already been used
This commit is contained in:
parent
6dc8d91ace
commit
32b37d7ba8
5 changed files with 111 additions and 22 deletions
|
@ -48,7 +48,9 @@ function extractTokenParts(options) {
|
|||
|
||||
if (!tokenParts) {
|
||||
return Promise.reject(new errors.UnauthorizedError({
|
||||
message: i18n.t('errors.api.common.invalidTokenStructure')
|
||||
message: i18n.t('errors.api.passwordReset.corruptedToken.message'),
|
||||
context: i18n.t('errors.api.passwordReset.corruptedToken.context'),
|
||||
help: i18n.t('errors.api.passwordReset.corruptedToken.help')
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -86,16 +88,29 @@ function doReset(options, tokenParts, settingsAPI) {
|
|||
throw new errors.NotFoundError({message: i18n.t('errors.api.users.userNotFound')});
|
||||
}
|
||||
|
||||
let tokenIsCorrect = security.tokens.resetToken.compare({
|
||||
let compareResult = security.tokens.resetToken.compare({
|
||||
token: resetToken,
|
||||
dbHash: dbHash,
|
||||
password: user.get('password')
|
||||
});
|
||||
|
||||
if (!tokenIsCorrect) {
|
||||
return Promise.reject(new errors.BadRequestError({
|
||||
message: i18n.t('errors.api.common.invalidTokenStructure')
|
||||
}));
|
||||
if (!compareResult.correct) {
|
||||
let error;
|
||||
if (compareResult.reason === 'expired' || compareResult.reason === 'invalid_expiry') {
|
||||
error = new errors.BadRequestError({
|
||||
message: i18n.t('errors.api.passwordReset.expired.message'),
|
||||
context: i18n.t('errors.api.passwordReset.expired.context'),
|
||||
help: i18n.t('errors.api.passwordReset.expired.help')
|
||||
});
|
||||
} else {
|
||||
error = new errors.BadRequestError({
|
||||
message: i18n.t('errors.api.passwordReset.invalidToken.message'),
|
||||
context: i18n.t('errors.api.passwordReset.invalidToken.context'),
|
||||
help: i18n.t('errors.api.passwordReset.invalidToken.help')
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
return models.User.changePassword({
|
||||
|
|
|
@ -302,7 +302,6 @@
|
|||
},
|
||||
"api": {
|
||||
"common": {
|
||||
"invalidTokenStructure": "Invalid token structure",
|
||||
"notImplemented": "The server does not support the functionality required to fulfill the request."
|
||||
},
|
||||
"authentication": {
|
||||
|
@ -318,6 +317,23 @@
|
|||
"checkEmailConfigInstructions": "Please see {url} for instructions on configuring email.",
|
||||
"notTheBlogOwner": "You are not the site owner."
|
||||
},
|
||||
"passwordReset": {
|
||||
"expired": {
|
||||
"message": "Cannot reset password.",
|
||||
"context": "Password reset link expired.",
|
||||
"help": "Request a new password reset via the login form."
|
||||
},
|
||||
"invalidToken": {
|
||||
"message": "Cannot reset password.",
|
||||
"context": "Password reset link has already been used.",
|
||||
"help": "Request a new password reset via the login form."
|
||||
},
|
||||
"corruptedToken": {
|
||||
"message": "Cannot reset password.",
|
||||
"context": "Invalid password reset link.",
|
||||
"help": "Check if password reset link has been fully copied or request new password reset via the login form."
|
||||
}
|
||||
},
|
||||
"configuration": {
|
||||
"invalidKey": "Invalid key"
|
||||
},
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
"@tryghost/members-ssr": "0.8.5",
|
||||
"@tryghost/mw-session-from-token": "0.1.7",
|
||||
"@tryghost/promise": "0.1.0",
|
||||
"@tryghost/security": "0.1.0",
|
||||
"@tryghost/security": "0.2.0",
|
||||
"@tryghost/session-service": "0.1.8",
|
||||
"@tryghost/social-urls": "0.1.12",
|
||||
"@tryghost/string": "0.1.11",
|
||||
|
|
|
@ -305,7 +305,72 @@ describe('Authentication API v3', function () {
|
|||
.then((res) => {
|
||||
should.exist(res.body.errors);
|
||||
res.body.errors[0].type.should.eql('UnauthorizedError');
|
||||
res.body.errors[0].message.should.eql('Invalid token structure');
|
||||
res.body.errors[0].message.should.eql('Cannot reset password.');
|
||||
res.body.errors[0].context.should.eql('Invalid password reset link.');
|
||||
});
|
||||
});
|
||||
|
||||
it('reset password: expired token', function () {
|
||||
return models.User.getOwnerUser(testUtils.context.internal)
|
||||
.then(function (ownerUser) {
|
||||
const dateInThePast = Date.now() - (1000 * 60);
|
||||
const token = security.tokens.resetToken.generateHash({
|
||||
expires: dateInThePast,
|
||||
email: user.email,
|
||||
dbHash: settingsCache.get('db_hash'),
|
||||
password: ownerUser.get('password')
|
||||
});
|
||||
|
||||
return request
|
||||
.put(localUtils.API.getApiQuery('authentication/passwordreset'))
|
||||
.set('Origin', config.get('url'))
|
||||
.set('Accept', 'application/json')
|
||||
.send({
|
||||
passwordreset: [{
|
||||
token: token,
|
||||
newPassword: 'thisissupersafe',
|
||||
ne2Password: 'thisissupersafe'
|
||||
}]
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(400);
|
||||
})
|
||||
.then((res) => {
|
||||
should.exist(res.body.errors);
|
||||
res.body.errors[0].type.should.eql('BadRequestError');
|
||||
res.body.errors[0].message.should.eql('Cannot reset password.');
|
||||
res.body.errors[0].context.should.eql('Password reset link expired.');
|
||||
});
|
||||
});
|
||||
|
||||
it('reset password: unmatched token', function () {
|
||||
const token = security.tokens.resetToken.generateHash({
|
||||
expires: Date.now() + (1000 * 60),
|
||||
email: user.email,
|
||||
dbHash: settingsCache.get('db_hash'),
|
||||
password: 'invalid_password'
|
||||
});
|
||||
|
||||
return request
|
||||
.put(localUtils.API.getApiQuery('authentication/passwordreset'))
|
||||
.set('Origin', config.get('url'))
|
||||
.set('Accept', 'application/json')
|
||||
.send({
|
||||
passwordreset: [{
|
||||
token: token,
|
||||
newPassword: 'thisissupersafe',
|
||||
ne2Password: 'thisissupersafe'
|
||||
}]
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(400)
|
||||
.then((res) => {
|
||||
should.exist(res.body.errors);
|
||||
res.body.errors[0].type.should.eql('BadRequestError');
|
||||
res.body.errors[0].message.should.eql('Cannot reset password.');
|
||||
res.body.errors[0].context.should.eql('Password reset link has already been used.');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
19
yarn.lock
19
yarn.lock
|
@ -556,15 +556,15 @@
|
|||
dependencies:
|
||||
bluebird "3.7.2"
|
||||
|
||||
"@tryghost/security@0.1.0":
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/security/-/security-0.1.0.tgz#ce78111dd6febb7705cf62d6838b0d8cdfb7df9f"
|
||||
integrity sha512-jDiLbsN9zCAZhGIibCb92Ym30BBWzVSZx2DBPL+Ige8b95pefEx6d8humbCjXhMIcJS5dlaMw4Pk70mXLCXCvQ==
|
||||
"@tryghost/security@0.2.0":
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/security/-/security-0.2.0.tgz#3f2ffa10bd933a21b19f659be9636146fb74a790"
|
||||
integrity sha512-plCxkYWD22mCQowH6boGakc2svv6Xqc7W1gJL007baBHHFR4XvWigseTxit1sb6FsI+GpzV16lBLtUUNiYTQTA==
|
||||
dependencies:
|
||||
"@tryghost/string" "0.1.10"
|
||||
"@tryghost/string" "0.1.11"
|
||||
bcryptjs "2.4.3"
|
||||
bluebird "3.7.2"
|
||||
lodash "4.17.19"
|
||||
lodash "4.17.20"
|
||||
|
||||
"@tryghost/session-service@0.1.8":
|
||||
version "0.1.8"
|
||||
|
@ -582,13 +582,6 @@
|
|||
ghost-ignition "4.2.2"
|
||||
lodash "4.17.19"
|
||||
|
||||
"@tryghost/string@0.1.10":
|
||||
version "0.1.10"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/string/-/string-0.1.10.tgz#6fb6a16cc628e6c22397c5889a7a85c29171374a"
|
||||
integrity sha512-TqkuNTvM6zOHIugAc3lnok4U4e4kKLvZz/Oo5Kka1jMHe70SrJZ3X1ljZEn7RloV3PdHmLUE5zN3Zvj2aC1avg==
|
||||
dependencies:
|
||||
unidecode "^0.1.8"
|
||||
|
||||
"@tryghost/string@0.1.11":
|
||||
version "0.1.11"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/string/-/string-0.1.11.tgz#259e101a6fa7d08870e5c3b12091f62eb61c3725"
|
||||
|
|
Loading…
Add table
Reference in a new issue