2014-07-11 16:17:09 +04:00
|
|
|
var _ = require('lodash'),
|
|
|
|
dataProvider = require('../models'),
|
2014-06-20 13:15:01 +04:00
|
|
|
settings = require('./settings'),
|
|
|
|
mail = require('./mail'),
|
2014-07-28 17:19:49 +04:00
|
|
|
globalUtils = require('../utils'),
|
2014-06-20 13:15:01 +04:00
|
|
|
utils = require('./utils'),
|
2014-08-17 10:17:23 +04:00
|
|
|
Promise = require('bluebird'),
|
2014-06-20 13:15:01 +04:00
|
|
|
errors = require('../errors'),
|
|
|
|
config = require('../config'),
|
|
|
|
authentication;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* ## Authentication API Methods
|
|
|
|
*
|
|
|
|
* **See:** [API Methods](index.js.html#api%20methods)
|
|
|
|
*/
|
|
|
|
authentication = {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* ## Generate Reset Token
|
|
|
|
* generate a reset token for a given email address
|
2014-09-10 08:06:24 +04:00
|
|
|
* @param {Object} object
|
2014-06-20 13:15:01 +04:00
|
|
|
* @returns {Promise(passwordreset)} message
|
|
|
|
*/
|
|
|
|
generateResetToken: function generateResetToken(object) {
|
2014-07-28 17:19:49 +04:00
|
|
|
var expires = Date.now() + globalUtils.ONE_DAY_MS,
|
2014-06-20 13:15:01 +04:00
|
|
|
email;
|
2014-07-02 18:22:18 +04:00
|
|
|
|
2014-07-19 00:40:47 +04:00
|
|
|
return authentication.isSetup().then(function (result) {
|
|
|
|
var setup = result.setup[0].status;
|
|
|
|
|
|
|
|
if (!setup) {
|
2014-08-17 10:17:23 +04:00
|
|
|
return Promise.reject(new errors.NoPermissionError('Setup must be completed before making this request.'));
|
2014-07-19 00:40:47 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
return utils.checkObject(object, 'passwordreset');
|
|
|
|
}).then(function (checkedPasswordReset) {
|
2014-06-20 13:15:01 +04:00
|
|
|
if (checkedPasswordReset.passwordreset[0].email) {
|
|
|
|
email = checkedPasswordReset.passwordreset[0].email;
|
|
|
|
} else {
|
2014-08-17 10:17:23 +04:00
|
|
|
return Promise.reject(new errors.BadRequestError('No email provided.'));
|
2014-06-20 13:15:01 +04:00
|
|
|
}
|
2014-06-27 23:08:16 +04:00
|
|
|
|
2014-10-22 01:18:45 +04:00
|
|
|
return settings.read({context: {internal: true}, key: 'dbHash'})
|
|
|
|
.then(function (response) {
|
2014-06-20 13:15:01 +04:00
|
|
|
var dbHash = response.settings[0].value;
|
2014-07-02 18:22:18 +04:00
|
|
|
return dataProvider.User.generateResetToken(email, expires, dbHash);
|
|
|
|
}).then(function (resetToken) {
|
2014-07-17 18:33:21 +04:00
|
|
|
var baseUrl = config.forceAdminSSL ? (config.urlSSL || config.url) : config.url,
|
2014-12-01 18:59:49 +03:00
|
|
|
resetUrl = baseUrl.replace(/\/$/, '') + '/ghost/reset/' + globalUtils.encodeBase64URLsafe(resetToken) + '/';
|
2014-07-31 04:15:34 +04:00
|
|
|
|
2014-09-10 08:06:24 +04:00
|
|
|
return mail.generateContent({data: {resetUrl: resetUrl}, template: 'reset-password'});
|
2014-07-31 04:15:34 +04:00
|
|
|
}).then(function (emailContent) {
|
|
|
|
var payload = {
|
|
|
|
mail: [{
|
|
|
|
message: {
|
|
|
|
to: email,
|
|
|
|
subject: 'Reset Password',
|
|
|
|
html: emailContent.html,
|
|
|
|
text: emailContent.text
|
|
|
|
},
|
|
|
|
options: {}
|
|
|
|
}]
|
|
|
|
};
|
|
|
|
return mail.send(payload, {context: {internal: true}});
|
2014-07-02 18:22:18 +04:00
|
|
|
}).then(function () {
|
2014-08-17 10:17:23 +04:00
|
|
|
return Promise.resolve({passwordreset: [{message: 'Check your email for further instructions.'}]});
|
|
|
|
}).catch(function (error) {
|
|
|
|
return Promise.reject(error);
|
2014-06-20 13:15:01 +04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* ## Reset Password
|
|
|
|
* reset password if a valid token and password (2x) is passed
|
2014-09-10 08:06:24 +04:00
|
|
|
* @param {Object} object
|
2014-06-20 13:15:01 +04:00
|
|
|
* @returns {Promise(passwordreset)} message
|
|
|
|
*/
|
|
|
|
resetPassword: function resetPassword(object) {
|
|
|
|
var resetToken,
|
|
|
|
newPassword,
|
|
|
|
ne2Password;
|
2014-07-19 00:40:47 +04:00
|
|
|
|
|
|
|
return authentication.isSetup().then(function (result) {
|
|
|
|
var setup = result.setup[0].status;
|
|
|
|
|
|
|
|
if (!setup) {
|
2014-08-17 10:17:23 +04:00
|
|
|
return Promise.reject(new errors.NoPermissionError('Setup must be completed before making this request.'));
|
2014-07-19 00:40:47 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
return utils.checkObject(object, 'passwordreset');
|
|
|
|
}).then(function (checkedPasswordReset) {
|
2014-06-20 13:15:01 +04:00
|
|
|
resetToken = checkedPasswordReset.passwordreset[0].token;
|
|
|
|
newPassword = checkedPasswordReset.passwordreset[0].newPassword;
|
|
|
|
ne2Password = checkedPasswordReset.passwordreset[0].ne2Password;
|
|
|
|
|
|
|
|
return settings.read({context: {internal: true}, key: 'dbHash'}).then(function (response) {
|
|
|
|
var dbHash = response.settings[0].value;
|
|
|
|
return dataProvider.User.resetPassword(resetToken, newPassword, ne2Password, dbHash);
|
|
|
|
}).then(function () {
|
2014-08-17 10:17:23 +04:00
|
|
|
return Promise.resolve({passwordreset: [{message: 'Password changed successfully.'}]});
|
|
|
|
}).catch(function (error) {
|
|
|
|
return Promise.reject(new errors.UnauthorizedError(error.message));
|
2014-06-20 13:15:01 +04:00
|
|
|
});
|
|
|
|
});
|
2014-07-03 19:06:07 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* ### Accept Invitation
|
|
|
|
* @param {User} object the user to create
|
|
|
|
* @returns {Promise(User}} Newly created user
|
|
|
|
*/
|
|
|
|
acceptInvitation: function acceptInvitation(object) {
|
|
|
|
var resetToken,
|
|
|
|
newPassword,
|
|
|
|
ne2Password,
|
|
|
|
name,
|
|
|
|
email;
|
|
|
|
|
2014-07-19 00:40:47 +04:00
|
|
|
return authentication.isSetup().then(function (result) {
|
|
|
|
var setup = result.setup[0].status;
|
|
|
|
|
|
|
|
if (!setup) {
|
2014-08-17 10:17:23 +04:00
|
|
|
return Promise.reject(new errors.NoPermissionError('Setup must be completed before making this request.'));
|
2014-07-19 00:40:47 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
return utils.checkObject(object, 'invitation');
|
|
|
|
}).then(function (checkedInvitation) {
|
2014-07-03 19:06:07 +04:00
|
|
|
resetToken = checkedInvitation.invitation[0].token;
|
|
|
|
newPassword = checkedInvitation.invitation[0].password;
|
|
|
|
ne2Password = checkedInvitation.invitation[0].password;
|
|
|
|
email = checkedInvitation.invitation[0].email;
|
|
|
|
name = checkedInvitation.invitation[0].name;
|
|
|
|
|
|
|
|
return settings.read({context: {internal: true}, key: 'dbHash'}).then(function (response) {
|
|
|
|
var dbHash = response.settings[0].value;
|
|
|
|
return dataProvider.User.resetPassword(resetToken, newPassword, ne2Password, dbHash);
|
|
|
|
}).then(function (user) {
|
2014-10-13 16:22:27 +04:00
|
|
|
// Setting the slug to '' has the model regenerate the slug from the user's name
|
|
|
|
return dataProvider.User.edit({name: name, email: email, slug: ''}, {id: user.id});
|
2014-07-03 19:06:07 +04:00
|
|
|
}).then(function () {
|
2014-08-17 10:17:23 +04:00
|
|
|
return Promise.resolve({invitation: [{message: 'Invitation accepted.'}]});
|
|
|
|
}).catch(function (error) {
|
|
|
|
return Promise.reject(new errors.UnauthorizedError(error.message));
|
2014-07-03 19:06:07 +04:00
|
|
|
});
|
|
|
|
});
|
2014-07-11 16:17:09 +04:00
|
|
|
},
|
|
|
|
|
2014-08-25 06:34:26 +04:00
|
|
|
/**
|
|
|
|
* ### Check for invitation
|
|
|
|
* @param {Object} options
|
|
|
|
* @param {string} options.email The email to check for an invitation on
|
|
|
|
* @returns {Promise(Invitation}} An invitation status
|
|
|
|
*/
|
|
|
|
isInvitation: function (options) {
|
|
|
|
return authentication.isSetup().then(function (result) {
|
|
|
|
var setup = result.setup[0].status;
|
|
|
|
|
|
|
|
if (!setup) {
|
|
|
|
return Promise.reject(new errors.NoPermissionError('Setup must be completed before making this request.'));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (options.email) {
|
|
|
|
return dataProvider.User.findOne({email: options.email, status: 'invited'}).then(function (response) {
|
|
|
|
if (response) {
|
|
|
|
return {invitation: [{valid: true}]};
|
|
|
|
} else {
|
|
|
|
return {invitation: [{valid: false}]};
|
|
|
|
}
|
|
|
|
});
|
2014-09-19 08:50:15 +04:00
|
|
|
} else {
|
|
|
|
return Promise.reject(new errors.BadRequestError('The server did not receive a valid email'));
|
2014-08-25 06:34:26 +04:00
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2014-07-11 16:17:09 +04:00
|
|
|
isSetup: function () {
|
2014-07-17 16:22:32 +04:00
|
|
|
return dataProvider.User.query(function (qb) {
|
2014-09-10 08:06:24 +04:00
|
|
|
qb.whereIn('status', ['active', 'warn-1', 'warn-2', 'warn-3', 'warn-4', 'locked']);
|
|
|
|
}).fetch().then(function (users) {
|
|
|
|
if (users) {
|
|
|
|
return Promise.resolve({setup: [{status: true}]});
|
|
|
|
} else {
|
|
|
|
return Promise.resolve({setup: [{status: false}]});
|
|
|
|
}
|
|
|
|
});
|
2014-07-11 16:17:09 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
setup: function (object) {
|
2014-07-15 15:03:12 +04:00
|
|
|
var setupUser,
|
|
|
|
internal = {context: {internal: true}};
|
2014-07-11 16:17:09 +04:00
|
|
|
|
2014-07-19 00:40:47 +04:00
|
|
|
return authentication.isSetup().then(function (result) {
|
|
|
|
var setup = result.setup[0].status;
|
|
|
|
|
|
|
|
if (setup) {
|
2014-08-17 10:17:23 +04:00
|
|
|
return Promise.reject(new errors.NoPermissionError('Setup has already been completed.'));
|
2014-07-19 00:40:47 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
return utils.checkObject(object, 'setup');
|
|
|
|
}).then(function (checkedSetupData) {
|
2014-07-11 16:17:09 +04:00
|
|
|
setupUser = {
|
|
|
|
name: checkedSetupData.setup[0].name,
|
|
|
|
email: checkedSetupData.setup[0].email,
|
|
|
|
password: checkedSetupData.setup[0].password,
|
|
|
|
blogTitle: checkedSetupData.setup[0].blogTitle,
|
|
|
|
status: 'active'
|
|
|
|
};
|
2014-07-15 15:03:12 +04:00
|
|
|
|
2014-08-05 22:11:17 +04:00
|
|
|
return dataProvider.User.findOne({role: 'Owner', status: 'all'});
|
2014-07-15 15:03:12 +04:00
|
|
|
}).then(function (ownerUser) {
|
|
|
|
if (ownerUser) {
|
|
|
|
return dataProvider.User.setup(setupUser, _.extend(internal, {id: ownerUser.id}));
|
2014-07-11 16:17:09 +04:00
|
|
|
} else {
|
2014-08-05 15:48:10 +04:00
|
|
|
return dataProvider.Role.findOne({name: 'Owner'}).then(function (ownerRole) {
|
|
|
|
setupUser.roles = [ownerRole.id];
|
|
|
|
return dataProvider.User.add(setupUser, internal);
|
|
|
|
});
|
2014-07-11 16:17:09 +04:00
|
|
|
}
|
|
|
|
}).then(function (user) {
|
|
|
|
var userSettings = [];
|
|
|
|
|
|
|
|
userSettings.push({key: 'email', value: setupUser.email});
|
|
|
|
|
|
|
|
// Handles the additional values set by the setup screen.
|
|
|
|
if (!_.isEmpty(setupUser.blogTitle)) {
|
|
|
|
userSettings.push({key: 'title', value: setupUser.blogTitle});
|
2014-08-08 18:47:56 +04:00
|
|
|
userSettings.push({key: 'description', value: 'Thoughts, stories and ideas.'});
|
2014-07-11 16:17:09 +04:00
|
|
|
}
|
2015-04-18 00:27:04 +03:00
|
|
|
setupUser = user.toJSON(internal);
|
2014-07-16 01:43:22 +04:00
|
|
|
return settings.edit({settings: userSettings}, {context: {user: setupUser.id}});
|
2014-07-11 16:17:09 +04:00
|
|
|
}).then(function () {
|
2014-07-23 05:22:13 +04:00
|
|
|
var data = {
|
|
|
|
ownerEmail: setupUser.email
|
|
|
|
};
|
|
|
|
|
2014-07-31 04:15:34 +04:00
|
|
|
return mail.generateContent({data: data, template: 'welcome'});
|
|
|
|
}).then(function (emailContent) {
|
|
|
|
var message = {
|
|
|
|
to: setupUser.email,
|
|
|
|
subject: 'Your New Ghost Blog',
|
|
|
|
html: emailContent.html,
|
|
|
|
text: emailContent.text
|
|
|
|
},
|
|
|
|
payload = {
|
|
|
|
mail: [{
|
|
|
|
message: message,
|
|
|
|
options: {}
|
|
|
|
}]
|
|
|
|
};
|
|
|
|
|
2014-08-17 10:17:23 +04:00
|
|
|
return mail.send(payload, {context: {internal: true}}).catch(function (error) {
|
2014-07-31 04:15:34 +04:00
|
|
|
errors.logError(
|
|
|
|
error.message,
|
2014-09-10 08:06:24 +04:00
|
|
|
'Unable to send welcome email, your blog will continue to function.',
|
|
|
|
'Please see http://support.ghost.org/mail/ for instructions on configuring email.'
|
2014-07-31 04:15:34 +04:00
|
|
|
);
|
2014-07-11 16:17:09 +04:00
|
|
|
});
|
|
|
|
}).then(function () {
|
2014-09-10 08:06:24 +04:00
|
|
|
return Promise.resolve({users: [setupUser]});
|
2014-07-11 16:17:09 +04:00
|
|
|
});
|
2014-09-01 22:02:46 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
revoke: function (object) {
|
|
|
|
var token;
|
|
|
|
|
|
|
|
if (object.token_type_hint && object.token_type_hint === 'access_token') {
|
|
|
|
token = dataProvider.Accesstoken;
|
|
|
|
} else if (object.token_type_hint && object.token_type_hint === 'refresh_token') {
|
|
|
|
token = dataProvider.Refreshtoken;
|
|
|
|
} else {
|
|
|
|
return errors.BadRequestError('Invalid token_type_hint given.');
|
|
|
|
}
|
|
|
|
|
2014-09-10 08:06:24 +04:00
|
|
|
return token.destroyByToken({token: object.token}).then(function () {
|
|
|
|
return Promise.resolve({token: object.token});
|
2014-09-01 22:02:46 +04:00
|
|
|
}, function () {
|
|
|
|
// On error we still want a 200. See https://tools.ietf.org/html/rfc7009#page-5
|
2014-09-10 08:06:24 +04:00
|
|
|
return Promise.resolve({token: object.token, error: 'Invalid token provided'});
|
2014-09-01 22:02:46 +04:00
|
|
|
});
|
2014-06-20 13:15:01 +04:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-07-19 00:40:47 +04:00
|
|
|
module.exports = authentication;
|