0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-03 23:00:14 -05:00
ghost/core/server/api/v0.1/authentication.js

409 lines
12 KiB
JavaScript
Raw Normal View History

const Promise = require('bluebird'),
{cloneDeep, assign} = require('lodash'),
validator = require('validator'),
config = require('../../config'),
common = require('../../lib/common'),
pipeline = require('../../lib/promise/pipeline'),
auth = require('../../services/auth'),
invitations = require('../../services/invitations'),
localUtils = require('./utils'),
models = require('../../models'),
web = require('../../web'),
mailAPI = require('./mail'),
settingsAPI = require('./settings');
let authentication;
function setupTasks(setupData) {
let tasks;
function validateData(setupData) {
return localUtils.checkObject(setupData, 'setup').then((checked) => {
const data = checked.setup[0];
return {
name: data.name,
email: data.email,
password: data.password,
blogTitle: data.blogTitle,
status: 'active'
};
});
}
function doSettings(data) {
return auth.setup.doSettings(data, settingsAPI);
}
function formatResponse(user) {
return user.toJSON({context: {internal: true}});
}
tasks = [
validateData,
auth.setup.setupUser,
doSettings,
formatResponse
];
return pipeline(tasks, setupData);
}
/**
* ## Authentication API Methods
*
* **See:** [API Methods](events.js.html#api%20methods)
*/
authentication = {
/**
* @description generate a reset token for a given email address
🎨 remove token logic from user model (#7622) * 🔥 remove User model functions - validateToken - generateToken - resetPassword - all this logic will re-appear in a different way Token logic: - was already extracted as separate PR, see https://github.com/TryGhost/Ghost/pull/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. * 🔥 remove password reset tests from User model - we already have unit tests for change password and the token logic - i will re-check at the end if any test case is missing - but for now i will just burn the tests * ✨ add token logic to controlling unit generateResetToken endpoint - the only change here is instead of calling the User model to generate a token, we generate the token via utils - we fetch the user by email, and generate a hash and return resetPassword endpoint - here we have changed a little bit more - first of all: we have added the validation check if the new passwords match - a new helper method to extract the token informations - the brute force security check, which can be handled later from the new bruteforce middleware (see TODO) - the actual reset function is doing the steps: load me the user, compare the token, change the password and activate the user - we can think of wrapping these steps into a User model function - i was not sure about it, because it is actually part of the controlling unit [ci skip] * 🎨 tidy up - jscs - jshint - naming functions - fixes * ✨ add a test for resetting the password - there was none - added a test to reset the password * 🎨 add more token tests - ensure quality - ensure logic we had * 🔥 remove compare new password check from User Model - this part of controlling unit * ✨ compare new passwords for user endpoint - we deleted the logic in User Model - we are adding the logic to controlling unit * 🐛 spam prevention forgotten can crash - no validation happend before this middleware - it just assumes that the root key is present - when we work on our API, we need to ensure that 1. pre validation happens 2. we call middlewares 3. ... * 🎨 token translation key
2016-11-07 12:18:50 +01:00
* @param {Object} object
* @returns {Promise<Object>} message
*/
generateResetToken(object) {
let tasks;
🎨 remove token logic from user model (#7622) * 🔥 remove User model functions - validateToken - generateToken - resetPassword - all this logic will re-appear in a different way Token logic: - was already extracted as separate PR, see https://github.com/TryGhost/Ghost/pull/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. * 🔥 remove password reset tests from User model - we already have unit tests for change password and the token logic - i will re-check at the end if any test case is missing - but for now i will just burn the tests * ✨ add token logic to controlling unit generateResetToken endpoint - the only change here is instead of calling the User model to generate a token, we generate the token via utils - we fetch the user by email, and generate a hash and return resetPassword endpoint - here we have changed a little bit more - first of all: we have added the validation check if the new passwords match - a new helper method to extract the token informations - the brute force security check, which can be handled later from the new bruteforce middleware (see TODO) - the actual reset function is doing the steps: load me the user, compare the token, change the password and activate the user - we can think of wrapping these steps into a User model function - i was not sure about it, because it is actually part of the controlling unit [ci skip] * 🎨 tidy up - jscs - jshint - naming functions - fixes * ✨ add a test for resetting the password - there was none - added a test to reset the password * 🎨 add more token tests - ensure quality - ensure logic we had * 🔥 remove compare new password check from User Model - this part of controlling unit * ✨ compare new passwords for user endpoint - we deleted the logic in User Model - we are adding the logic to controlling unit * 🐛 spam prevention forgotten can crash - no validation happend before this middleware - it just assumes that the root key is present - when we work on our API, we need to ensure that 1. pre validation happens 2. we call middlewares 3. ... * 🎨 token translation key
2016-11-07 12:18:50 +01:00
function validateRequest(object) {
return localUtils.checkObject(object, 'passwordreset').then((data) => {
const email = data.passwordreset[0].email;
if (typeof email !== 'string' || !validator.isEmail(email)) {
throw new common.errors.BadRequestError({
message: common.i18n.t('errors.api.authentication.noEmailProvided')
});
}
return email;
});
}
function generateToken(email) {
return auth.passwordreset.generateToken(email, settingsAPI);
}
function sendResetNotification(data) {
return auth.passwordreset.sendResetNotification(data, mailAPI);
}
function formatResponse() {
return {
passwordreset: [
{message: common.i18n.t('common.api.authentication.mail.checkEmailForInstructions')}
]
};
}
tasks = [
auth.setup.assertSetupCompleted(true),
validateRequest,
generateToken,
sendResetNotification,
formatResponse
];
🎨 remove token logic from user model (#7622) * 🔥 remove User model functions - validateToken - generateToken - resetPassword - all this logic will re-appear in a different way Token logic: - was already extracted as separate PR, see https://github.com/TryGhost/Ghost/pull/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. * 🔥 remove password reset tests from User model - we already have unit tests for change password and the token logic - i will re-check at the end if any test case is missing - but for now i will just burn the tests * ✨ add token logic to controlling unit generateResetToken endpoint - the only change here is instead of calling the User model to generate a token, we generate the token via utils - we fetch the user by email, and generate a hash and return resetPassword endpoint - here we have changed a little bit more - first of all: we have added the validation check if the new passwords match - a new helper method to extract the token informations - the brute force security check, which can be handled later from the new bruteforce middleware (see TODO) - the actual reset function is doing the steps: load me the user, compare the token, change the password and activate the user - we can think of wrapping these steps into a User model function - i was not sure about it, because it is actually part of the controlling unit [ci skip] * 🎨 tidy up - jscs - jshint - naming functions - fixes * ✨ add a test for resetting the password - there was none - added a test to reset the password * 🎨 add more token tests - ensure quality - ensure logic we had * 🔥 remove compare new password check from User Model - this part of controlling unit * ✨ compare new passwords for user endpoint - we deleted the logic in User Model - we are adding the logic to controlling unit * 🐛 spam prevention forgotten can crash - no validation happend before this middleware - it just assumes that the root key is present - when we work on our API, we need to ensure that 1. pre validation happens 2. we call middlewares 3. ... * 🎨 token translation key
2016-11-07 12:18:50 +01:00
return pipeline(tasks, object);
},
/**
* ## Reset Password
* reset password if a valid token and password (2x) is passed
🎨 remove token logic from user model (#7622) * 🔥 remove User model functions - validateToken - generateToken - resetPassword - all this logic will re-appear in a different way Token logic: - was already extracted as separate PR, see https://github.com/TryGhost/Ghost/pull/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. * 🔥 remove password reset tests from User model - we already have unit tests for change password and the token logic - i will re-check at the end if any test case is missing - but for now i will just burn the tests * ✨ add token logic to controlling unit generateResetToken endpoint - the only change here is instead of calling the User model to generate a token, we generate the token via utils - we fetch the user by email, and generate a hash and return resetPassword endpoint - here we have changed a little bit more - first of all: we have added the validation check if the new passwords match - a new helper method to extract the token informations - the brute force security check, which can be handled later from the new bruteforce middleware (see TODO) - the actual reset function is doing the steps: load me the user, compare the token, change the password and activate the user - we can think of wrapping these steps into a User model function - i was not sure about it, because it is actually part of the controlling unit [ci skip] * 🎨 tidy up - jscs - jshint - naming functions - fixes * ✨ add a test for resetting the password - there was none - added a test to reset the password * 🎨 add more token tests - ensure quality - ensure logic we had * 🔥 remove compare new password check from User Model - this part of controlling unit * ✨ compare new passwords for user endpoint - we deleted the logic in User Model - we are adding the logic to controlling unit * 🐛 spam prevention forgotten can crash - no validation happend before this middleware - it just assumes that the root key is present - when we work on our API, we need to ensure that 1. pre validation happens 2. we call middlewares 3. ... * 🎨 token translation key
2016-11-07 12:18:50 +01:00
* @param {Object} object
* @returns {Promise<Object>} message
*/
resetPassword(object, opts) {
let tasks;
const options = {context: {internal: true}};
🎨 remove token logic from user model (#7622) * 🔥 remove User model functions - validateToken - generateToken - resetPassword - all this logic will re-appear in a different way Token logic: - was already extracted as separate PR, see https://github.com/TryGhost/Ghost/pull/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. * 🔥 remove password reset tests from User model - we already have unit tests for change password and the token logic - i will re-check at the end if any test case is missing - but for now i will just burn the tests * ✨ add token logic to controlling unit generateResetToken endpoint - the only change here is instead of calling the User model to generate a token, we generate the token via utils - we fetch the user by email, and generate a hash and return resetPassword endpoint - here we have changed a little bit more - first of all: we have added the validation check if the new passwords match - a new helper method to extract the token informations - the brute force security check, which can be handled later from the new bruteforce middleware (see TODO) - the actual reset function is doing the steps: load me the user, compare the token, change the password and activate the user - we can think of wrapping these steps into a User model function - i was not sure about it, because it is actually part of the controlling unit [ci skip] * 🎨 tidy up - jscs - jshint - naming functions - fixes * ✨ add a test for resetting the password - there was none - added a test to reset the password * 🎨 add more token tests - ensure quality - ensure logic we had * 🔥 remove compare new password check from User Model - this part of controlling unit * ✨ compare new passwords for user endpoint - we deleted the logic in User Model - we are adding the logic to controlling unit * 🐛 spam prevention forgotten can crash - no validation happend before this middleware - it just assumes that the root key is present - when we work on our API, we need to ensure that 1. pre validation happens 2. we call middlewares 3. ... * 🎨 token translation key
2016-11-07 12:18:50 +01:00
function validateRequest() {
return localUtils.validate('passwordreset')(object, options)
.then((options) => {
const data = options.data.passwordreset[0];
🎨 remove token logic from user model (#7622) * 🔥 remove User model functions - validateToken - generateToken - resetPassword - all this logic will re-appear in a different way Token logic: - was already extracted as separate PR, see https://github.com/TryGhost/Ghost/pull/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. * 🔥 remove password reset tests from User model - we already have unit tests for change password and the token logic - i will re-check at the end if any test case is missing - but for now i will just burn the tests * ✨ add token logic to controlling unit generateResetToken endpoint - the only change here is instead of calling the User model to generate a token, we generate the token via utils - we fetch the user by email, and generate a hash and return resetPassword endpoint - here we have changed a little bit more - first of all: we have added the validation check if the new passwords match - a new helper method to extract the token informations - the brute force security check, which can be handled later from the new bruteforce middleware (see TODO) - the actual reset function is doing the steps: load me the user, compare the token, change the password and activate the user - we can think of wrapping these steps into a User model function - i was not sure about it, because it is actually part of the controlling unit [ci skip] * 🎨 tidy up - jscs - jshint - naming functions - fixes * ✨ add a test for resetting the password - there was none - added a test to reset the password * 🎨 add more token tests - ensure quality - ensure logic we had * 🔥 remove compare new password check from User Model - this part of controlling unit * ✨ compare new passwords for user endpoint - we deleted the logic in User Model - we are adding the logic to controlling unit * 🐛 spam prevention forgotten can crash - no validation happend before this middleware - it just assumes that the root key is present - when we work on our API, we need to ensure that 1. pre validation happens 2. we call middlewares 3. ... * 🎨 token translation key
2016-11-07 12:18:50 +01:00
if (data.newPassword !== data.ne2Password) {
return Promise.reject(new common.errors.ValidationError({
message: common.i18n.t('errors.models.user.newPasswordsDoNotMatch')
🎨 remove token logic from user model (#7622) * 🔥 remove User model functions - validateToken - generateToken - resetPassword - all this logic will re-appear in a different way Token logic: - was already extracted as separate PR, see https://github.com/TryGhost/Ghost/pull/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. * 🔥 remove password reset tests from User model - we already have unit tests for change password and the token logic - i will re-check at the end if any test case is missing - but for now i will just burn the tests * ✨ add token logic to controlling unit generateResetToken endpoint - the only change here is instead of calling the User model to generate a token, we generate the token via utils - we fetch the user by email, and generate a hash and return resetPassword endpoint - here we have changed a little bit more - first of all: we have added the validation check if the new passwords match - a new helper method to extract the token informations - the brute force security check, which can be handled later from the new bruteforce middleware (see TODO) - the actual reset function is doing the steps: load me the user, compare the token, change the password and activate the user - we can think of wrapping these steps into a User model function - i was not sure about it, because it is actually part of the controlling unit [ci skip] * 🎨 tidy up - jscs - jshint - naming functions - fixes * ✨ add a test for resetting the password - there was none - added a test to reset the password * 🎨 add more token tests - ensure quality - ensure logic we had * 🔥 remove compare new password check from User Model - this part of controlling unit * ✨ compare new passwords for user endpoint - we deleted the logic in User Model - we are adding the logic to controlling unit * 🐛 spam prevention forgotten can crash - no validation happend before this middleware - it just assumes that the root key is present - when we work on our API, we need to ensure that 1. pre validation happens 2. we call middlewares 3. ... * 🎨 token translation key
2016-11-07 12:18:50 +01:00
}));
}
return Promise.resolve(options);
});
}
function doReset({options, tokenParts}) {
return auth.passwordreset.doReset(options, tokenParts, settingsAPI)
.then((params) => {
web.shared.middlewares.api.spamPrevention.userLogin().reset(opts.ip, `${tokenParts.email}login`);
return params;
});
}
function formatResponse() {
return {
passwordreset: [
{message: common.i18n.t('common.api.authentication.mail.passwordChanged')}
]
};
}
tasks = [
validateRequest,
auth.setup.assertSetupCompleted(true),
auth.passwordreset.extractTokenParts,
auth.passwordreset.protectBruteForce,
doReset,
formatResponse
];
🎨 remove token logic from user model (#7622) * 🔥 remove User model functions - validateToken - generateToken - resetPassword - all this logic will re-appear in a different way Token logic: - was already extracted as separate PR, see https://github.com/TryGhost/Ghost/pull/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. * 🔥 remove password reset tests from User model - we already have unit tests for change password and the token logic - i will re-check at the end if any test case is missing - but for now i will just burn the tests * ✨ add token logic to controlling unit generateResetToken endpoint - the only change here is instead of calling the User model to generate a token, we generate the token via utils - we fetch the user by email, and generate a hash and return resetPassword endpoint - here we have changed a little bit more - first of all: we have added the validation check if the new passwords match - a new helper method to extract the token informations - the brute force security check, which can be handled later from the new bruteforce middleware (see TODO) - the actual reset function is doing the steps: load me the user, compare the token, change the password and activate the user - we can think of wrapping these steps into a User model function - i was not sure about it, because it is actually part of the controlling unit [ci skip] * 🎨 tidy up - jscs - jshint - naming functions - fixes * ✨ add a test for resetting the password - there was none - added a test to reset the password * 🎨 add more token tests - ensure quality - ensure logic we had * 🔥 remove compare new password check from User Model - this part of controlling unit * ✨ compare new passwords for user endpoint - we deleted the logic in User Model - we are adding the logic to controlling unit * 🐛 spam prevention forgotten can crash - no validation happend before this middleware - it just assumes that the root key is present - when we work on our API, we need to ensure that 1. pre validation happens 2. we call middlewares 3. ... * 🎨 token translation key
2016-11-07 12:18:50 +01:00
return pipeline(tasks, object, options);
},
/**
* ### Accept Invitation
* @param {Object} invitation an invitation object
* @returns {Promise<Object>}
*/
acceptInvitation(invitation) {
let tasks;
function validateInvitation(invitation) {
return localUtils.checkObject(invitation, 'invitation')
.then(() => {
if (!invitation.invitation[0].token) {
return Promise.reject(new common.errors.ValidationError({message: common.i18n.t('errors.api.authentication.noTokenProvided')}));
}
if (!invitation.invitation[0].email) {
return Promise.reject(new common.errors.ValidationError({message: common.i18n.t('errors.api.authentication.noEmailProvided')}));
}
if (!invitation.invitation[0].password) {
return Promise.reject(new common.errors.ValidationError({message: common.i18n.t('errors.api.authentication.noPasswordProvided')}));
}
if (!invitation.invitation[0].name) {
return Promise.reject(new common.errors.ValidationError({message: common.i18n.t('errors.api.authentication.noNameProvided')}));
}
return invitation;
});
}
function formatResponse() {
return {
invitation: [
{message: common.i18n.t('common.api.authentication.mail.invitationAccepted')}
]
};
}
tasks = [
auth.setup.assertSetupCompleted(true),
validateInvitation,
invitations.accept,
formatResponse
];
return pipeline(tasks, invitation);
},
/**
* ### Check for invitation
* @param {Object} options
* @returns {Promise<Object>} An invitation status
*/
isInvitation(options) {
let tasks;
const localOptions = cloneDeep(options || {});
function processArgs(options) {
const email = options.email;
if (typeof email !== 'string' || !validator.isEmail(email)) {
throw new common.errors.BadRequestError({
message: common.i18n.t('errors.api.authentication.invalidEmailReceived')
});
}
return email;
}
function checkInvitation(email) {
return models.Invite
.findOne({email: email, status: 'sent'}, options)
.then((invite) => {
if (!invite) {
return {invitation: [{valid: false}]};
}
return {invitation: [{valid: true}]};
});
}
tasks = [
processArgs,
auth.setup.assertSetupCompleted(true),
checkInvitation
];
return pipeline(tasks, localOptions);
},
/**
* Checks the setup status
* @return {Promise}
*/
isSetup() {
let tasks;
function formatResponse(isSetup) {
return {
setup: [
{
status: isSetup,
// Pre-populate from config if, and only if the values exist in config.
title: config.title || undefined,
name: config.user_name || undefined,
email: config.user_email || undefined
}
]
};
}
tasks = [
auth.setup.checkIsSetup,
formatResponse
];
return pipeline(tasks);
},
/**
* Executes the setup tasks and sends an email to the owner
* @param {Object} setupDetails
* @return {Promise<Object>} a user api payload
*/
setup(setupDetails) {
let tasks;
function doSetup(setupDetails) {
return setupTasks(setupDetails);
}
function sendNotification(setupUser) {
return auth.setup.sendWelcomeEmail(setupUser.email, mailAPI)
.then(() => setupUser);
}
function formatResponse(setupUser) {
return {users: [setupUser]};
}
tasks = [
auth.setup.assertSetupCompleted(false),
doSetup,
sendNotification,
formatResponse
];
return pipeline(tasks, setupDetails);
},
/**
* Updates the blog setup
* @param {Object} setupDetails request payload with setup details
* @param {Object} options
* @return {Promise<Object>} a User API response payload
*/
updateSetup(setupDetails, options) {
let tasks;
const localOptions = cloneDeep(options || {});
function processArgs(setupDetails, options) {
if (!options.context || !options.context.user) {
throw new common.errors.NoPermissionError({message: common.i18n.t('errors.api.authentication.notTheBlogOwner')});
}
return assign({setupDetails: setupDetails}, options);
}
function checkPermission(options) {
return models.User.findOne({role: 'Owner', status: 'all'})
.then((owner) => {
if (owner.id !== options.context.user) {
throw new common.errors.NoPermissionError({message: common.i18n.t('errors.api.authentication.notTheBlogOwner')});
}
return options.setupDetails;
});
}
function formatResponse(user) {
return {users: [user]};
}
tasks = [
processArgs,
auth.setup.assertSetupCompleted(true),
checkPermission,
setupTasks,
formatResponse
];
return pipeline(tasks, setupDetails, localOptions);
},
/**
* Revokes a bearer token.
* @param {Object} tokenDetails
* @param {Object} options
* @return {Promise<Object>} an object containing the revoked token.
*/
revoke(tokenDetails, options) {
let tasks;
const localOptions = cloneDeep(options || {});
function processArgs(tokenDetails, options) {
return assign({}, tokenDetails, options);
}
function revokeToken(options) {
const providers = [
models.Refreshtoken,
models.Accesstoken
],
response = {token: options.token};
function destroyToken(provider, options, providers) {
return provider.destroyByToken(options)
.return(response)
.catch(provider.NotFoundError, () => {
if (!providers.length) {
return {
token: tokenDetails.token,
error: common.i18n.t('errors.api.authentication.invalidTokenProvided')
};
}
return destroyToken(providers.pop(), options, providers);
})
.catch(() => {
throw new common.errors.TokenRevocationError({
message: common.i18n.t('errors.api.authentication.tokenRevocationFailed')
});
});
}
return destroyToken(providers.pop(), options, providers);
}
tasks = [
processArgs,
revokeToken
];
return pipeline(tasks, tokenDetails, localOptions);
}
};
module.exports = authentication;