Skip to content

Commit

Permalink
🔥 remove User model functions
Browse files Browse the repository at this point in the history
- validateToken
- generateToken
- resetPassword
- all this logic will re-appear in a different way

Token logic:
- was already extracted as separate PR, see TryGhost#7554
- we will use this logic in the controller, you will see in the next commits

Reset Password:
Was just a wrapper for calling the token logic and change the password.
We can reconsider keeping the function to call: changePassword and activate the status of the user - but i think it's fine to trigger these two actions from the controlling unit.
  • Loading branch information
kirrg001 committed Oct 27, 2016
1 parent e58d8ca commit a24d7c0
Showing 1 changed file with 0 additions and 108 deletions.
108 changes: 0 additions & 108 deletions core/server/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -707,114 +707,6 @@ User = ghostBookshelf.Model.extend({
});
},

generateResetToken: function generateResetToken(email, expires, dbHash) {
return this.getByEmail(email).then(function then(foundUser) {
if (!foundUser) {
return Promise.reject(new errors.NotFoundError({message: i18n.t('errors.models.user.noUserWithEnteredEmailAddr')}));
}

var hash = crypto.createHash('sha256'),
text = '';

// Token:
// BASE64(TIMESTAMP + email + HASH(TIMESTAMP + email + oldPasswordHash + dbHash ))
hash.update(String(expires));
hash.update(email.toLocaleLowerCase());
hash.update(foundUser.get('password'));
hash.update(String(dbHash));

text += [expires, email, hash.digest('base64')].join('|');
return new Buffer(text).toString('base64');
});
},

validateToken: function validateToken(token, dbHash) {
/*jslint bitwise:true*/
// TODO: Is there a chance the use of ascii here will cause problems if oldPassword has weird characters?
var tokenText = new Buffer(token, 'base64').toString('ascii'),
parts,
expires,
email;

parts = tokenText.split('|');

// Check if invalid structure
if (!parts || parts.length !== 3) {
return Promise.reject(new errors.BadRequestError({message: i18n.t('errors.models.user.invalidTokenStructure')}));
}

expires = parseInt(parts[0], 10);
email = parts[1];

if (isNaN(expires)) {
return Promise.reject(new errors.BadRequestError({message: i18n.t('errors.models.user.invalidTokenExpiration')}));
}

// Check if token is expired to prevent replay attacks
if (expires < Date.now()) {
return Promise.reject(new errors.ValidationError({message: i18n.t('errors.models.user.expiredToken')}));
}

// to prevent brute force attempts to reset the password the combination of email+expires is only allowed for
// 10 attempts
if (tokenSecurity[email + '+' + expires] && tokenSecurity[email + '+' + expires].count >= 10) {
return Promise.reject(new errors.NoPermissionError({message: i18n.t('errors.models.user.tokenLocked')}));
}

return this.generateResetToken(email, expires, dbHash).then(function then(generatedToken) {
// Check for matching tokens with timing independent comparison
var diff = 0,
i;

// check if the token length is correct
if (token.length !== generatedToken.length) {
diff = 1;
}

for (i = token.length - 1; i >= 0; i = i - 1) {
diff |= token.charCodeAt(i) ^ generatedToken.charCodeAt(i);
}

if (diff === 0) {
return email;
}

// increase the count for email+expires for each failed attempt
tokenSecurity[email + '+' + expires] = {
count: tokenSecurity[email + '+' + expires] ? tokenSecurity[email + '+' + expires].count + 1 : 1
};
return Promise.reject(new errors.BadRequestError({message: i18n.t('errors.models.user.invalidToken')}));
});
},

resetPassword: function resetPassword(options) {
var self = this,
token = options.token,
newPassword = options.newPassword,
ne2Password = options.ne2Password,
dbHash = options.dbHash;

if (newPassword !== ne2Password) {
return Promise.reject(new errors.ValidationError({
message: i18n.t('errors.models.user.newPasswordsDoNotMatch')
}));
}

// Validate the token; returns the email address from token
return self.validateToken(utils.decodeBase64URLsafe(token), dbHash).then(function then(email) {
return self.getByEmail(email);
}).then(function then(foundUser) {
if (!foundUser) {
return Promise.reject(new errors.NotFoundError({message: i18n.t('errors.models.user.userNotFound')}));
}

return foundUser.save({
status: 'active',
password: newPassword
});
});
},

transferOwnership: function transferOwnership(object, options) {
var ownerRole,
contextUser;
Expand Down

0 comments on commit a24d7c0

Please sign in to comment.