mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-03 23:00:14 -05:00
Merge pull request #4554 from sebgie/issue#3872
URL safe base64 encoding
This commit is contained in:
commit
c06e649ab1
5 changed files with 41 additions and 15 deletions
|
@ -47,7 +47,7 @@ authentication = {
|
|||
return dataProvider.User.generateResetToken(email, expires, dbHash);
|
||||
}).then(function (resetToken) {
|
||||
var baseUrl = config.forceAdminSSL ? (config.urlSSL || config.url) : config.url,
|
||||
resetUrl = baseUrl.replace(/\/$/, '') + '/ghost/reset/' + resetToken + '/';
|
||||
resetUrl = baseUrl.replace(/\/$/, '') + '/ghost/reset/' + globalUtils.encodeBase64URLsafe(resetToken) + '/';
|
||||
|
||||
return mail.generateContent({data: {resetUrl: resetUrl}, template: 'reset-password'});
|
||||
}).then(function (emailContent) {
|
||||
|
|
|
@ -46,7 +46,7 @@ sendInviteEmail = function sendInviteEmail(user) {
|
|||
}).then(function (resetToken) {
|
||||
var baseUrl = config.forceAdminSSL ? (config.urlSSL || config.url) : config.url;
|
||||
|
||||
emailData.resetLink = baseUrl.replace(/\/$/, '') + '/ghost/signup/' + resetToken + '/';
|
||||
emailData.resetLink = baseUrl.replace(/\/$/, '') + '/ghost/signup/' + globalUtils.encodeBase64URLsafe(resetToken) + '/';
|
||||
|
||||
return mail.generateContent({data: emailData, template: 'invite-user'});
|
||||
}).then(function (emailContent) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
var _ = require('lodash'),
|
||||
Promise = require('bluebird'),
|
||||
errors = require('../errors'),
|
||||
utils = require('../utils'),
|
||||
bcrypt = require('bcryptjs'),
|
||||
ghostBookshelf = require('./base'),
|
||||
crypto = require('crypto'),
|
||||
|
@ -721,28 +722,20 @@ User = ghostBookshelf.Model.extend({
|
|||
text = '';
|
||||
|
||||
// Token:
|
||||
// BASE64(TIMESTAMP + email + HASH(TIMESTAMP + email + oldPasswordHash + dbHash )).replace('=', '-')
|
||||
|
||||
// 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('|');
|
||||
|
||||
// it's possible that the token might get URI encoded, which breaks it
|
||||
// we replace any `=`s with `-`s as they aren't valid base64 characters
|
||||
// but are valid in a URL, so won't suffer encoding issues
|
||||
return new Buffer(text).toString('base64').replace('=', '-');
|
||||
return new Buffer(text).toString('base64');
|
||||
});
|
||||
},
|
||||
|
||||
validateToken: function (token, dbHash) {
|
||||
/*jslint bitwise:true*/
|
||||
// TODO: Is there a chance the use of ascii here will cause problems if oldPassword has weird characters?
|
||||
// We replaced `=`s with `-`s when we sent the token via email, so
|
||||
// now we reverse that change to get a valid base64 string to decode
|
||||
token = token.replace('-', '=');
|
||||
var tokenText = new Buffer(token, 'base64').toString('ascii'),
|
||||
parts,
|
||||
expires,
|
||||
|
@ -808,7 +801,7 @@ User = ghostBookshelf.Model.extend({
|
|||
|
||||
return validatePasswordLength(newPassword).then(function () {
|
||||
// Validate the token; returns the email address from token
|
||||
return self.validateToken(token, dbHash);
|
||||
return self.validateToken(utils.decodeBase64URLsafe(token), dbHash);
|
||||
}).then(function (email) {
|
||||
// Fetch the user by email, and hash the password at the same time.
|
||||
return Promise.join(
|
||||
|
|
|
@ -64,6 +64,19 @@ utils = {
|
|||
.toLowerCase();
|
||||
|
||||
return string;
|
||||
},
|
||||
// The token is encoded URL safe by replcaing '+' with '-', '\' with '_' and removing '='
|
||||
// NOTE: the token is not encoded using valid base64 anymore
|
||||
encodeBase64URLsafe: function (base64String) {
|
||||
return base64String.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
||||
},
|
||||
// Decode url safe base64 encoding and add padding ('=')
|
||||
decodeBase64URLsafe: function (base64String) {
|
||||
base64String = base64String.replace(/-/g, '+').replace(/_/g, '/');
|
||||
while (base64String.length % 4) {
|
||||
base64String += '=';
|
||||
}
|
||||
return base64String;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ var testUtils = require('../../utils'),
|
|||
_ = require('lodash'),
|
||||
|
||||
// Stuff we are testing
|
||||
utils = require('../../../server/utils'),
|
||||
UserModel = require('../../../server/models/user').User,
|
||||
RoleModel = require('../../../server/models/role').Role,
|
||||
context = testUtils.context.admin,
|
||||
|
@ -354,7 +355,7 @@ describe('User Model', function run() {
|
|||
});
|
||||
|
||||
describe('Password Reset', function () {
|
||||
beforeEach(testUtils.setup('owner'));
|
||||
beforeEach(testUtils.setup('users:roles'));
|
||||
|
||||
it('can generate reset token', function (done) {
|
||||
// Expires in one minute
|
||||
|
@ -378,7 +379,7 @@ describe('User Model', function run() {
|
|||
dbHash = uuid.v4();
|
||||
|
||||
UserModel.findAll().then(function (results) {
|
||||
return UserModel.generateResetToken(results.models[0].attributes.email, expires, dbHash);
|
||||
return UserModel.generateResetToken(results.models[1].attributes.email, expires, dbHash);
|
||||
}).then(function (token) {
|
||||
return UserModel.validateToken(token, dbHash);
|
||||
}).then(function () {
|
||||
|
@ -386,6 +387,24 @@ describe('User Model', function run() {
|
|||
}).catch(done);
|
||||
});
|
||||
|
||||
it('can validate an URI encoded reset token', function (done) {
|
||||
// Expires in one minute
|
||||
var expires = Date.now() + 60000,
|
||||
dbHash = uuid.v4();
|
||||
|
||||
UserModel.findAll().then(function (results) {
|
||||
return UserModel.generateResetToken(results.models[1].attributes.email, expires, dbHash);
|
||||
}).then(function (token) {
|
||||
token = utils.encodeBase64URLsafe(token);
|
||||
token = encodeURIComponent(token);
|
||||
token = decodeURIComponent(token);
|
||||
token = utils.decodeBase64URLsafe(token);
|
||||
return UserModel.validateToken(token, dbHash);
|
||||
}).then(function () {
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('can reset a password with a valid token', function (done) {
|
||||
// Expires in one minute
|
||||
var origPassword,
|
||||
|
@ -400,6 +419,7 @@ describe('User Model', function run() {
|
|||
|
||||
return UserModel.generateResetToken(firstUser.attributes.email, expires, dbHash);
|
||||
}).then(function (token) {
|
||||
token = utils.encodeBase64URLsafe(token);
|
||||
return UserModel.resetPassword(token, 'newpassword', 'newpassword', dbHash);
|
||||
}).then(function (resetUser) {
|
||||
var resetPassword = resetUser.get('password');
|
||||
|
|
Loading…
Add table
Reference in a new issue