0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-03 23:00:14 -05:00

Deleted all but active sessions on password change (#11639)

closes #10323 

* Fixed usage of hasMany for user->session
* Refactored changePassword to async function
* Deleted all user sessions when password changed
* Tested for session retained after password changed
* Added the session to the frame
* Skipped the current session when changing password
This commit is contained in:
Fabien O'Carroll 2020-03-05 12:22:32 +02:00 committed by GitHub
parent 7e1c7ef9d2
commit 58187175c3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 36 additions and 23 deletions

View file

@ -160,6 +160,7 @@ module.exports = {
}
},
query(frame) {
frame.options.skipSessionID = frame.original.session.id;
return models.User.changePassword(frame.data.password[0], frame.options);
}
},

View file

@ -40,6 +40,7 @@ const http = (apiImpl) => {
query: req.query,
params: req.params,
user: req.user,
session: req.session,
context: {
api_key: apiKey,
user: user,

View file

@ -155,6 +155,7 @@ module.exports = {
}
},
query(frame) {
frame.options.skipSessionID = frame.original.session.id;
return models.User.changePassword(frame.data.password[0], frame.options);
}
},

View file

@ -235,7 +235,7 @@ User = ghostBookshelf.Model.extend({
},
sessions: function sessions() {
return this.hasMany('Sessions');
return this.hasMany('Session');
},
roles: function roles() {
@ -864,31 +864,36 @@ User = ghostBookshelf.Model.extend({
* @param {Object} object
* @param {Object} unfilteredOptions
*/
changePassword: function changePassword(object, unfilteredOptions) {
var options = this.filterOptions(unfilteredOptions, 'changePassword'),
self = this,
newPassword = object.newPassword,
userId = object.user_id,
oldPassword = object.oldPassword,
isLoggedInUser = userId === options.context.user,
user;
changePassword: async function changePassword(object, unfilteredOptions) {
const options = this.filterOptions(unfilteredOptions, 'changePassword');
const newPassword = object.newPassword;
const userId = object.user_id;
const oldPassword = object.oldPassword;
const isLoggedInUser = userId === options.context.user;
const skipSessionID = unfilteredOptions.skipSessionID;
options.require = true;
options.withRelated = ['sessions'];
return self.forge({id: userId}).fetch(options)
.then(function then(_user) {
user = _user;
const user = await this.forge({id: userId}).fetch(options);
if (isLoggedInUser) {
return self.isPasswordCorrect({
plainPassword: oldPassword,
hashedPassword: user.get('password')
});
}
})
.then(function then() {
return user.save({password: newPassword});
if (isLoggedInUser) {
await this.isPasswordCorrect({
plainPassword: oldPassword,
hashedPassword: user.get('password')
});
}
const updatedUser = await user.save({password: newPassword});
const sessions = user.related('sessions');
for (const session of sessions) {
if (session.get('session_id') !== skipSessionID) {
await session.destroy(options);
}
}
return updatedUser;
},
transferOwnership: function transferOwnership(object, unfilteredOptions) {

View file

@ -310,8 +310,8 @@ describe('User API', function () {
});
});
it('Can change password', function () {
return request
it('Can change password and retain the session', async function () {
await request
.put(localUtils.API.getApiQuery('users/password'))
.set('Origin', config.get('url'))
.send({
@ -329,5 +329,10 @@ describe('User API', function () {
should.exist(res.body.password);
should.exist(res.body.password[0].message);
});
await request
.get(localUtils.API.getApiQuery('session/'))
.set('Origin', config.get('url'))
.expect(200);
});
});