Ghost/core/server/services/members/api.js
Fabien O'Carroll 078060abdc
Refactored members service logging and errors (#10919)
* Installed @tryghost/members-ssr@0.2.1

refs https://github.com/TryGhost/Members/issues/38

This updates allows for dynamic access of the membersApi, which will be
used in future when replacing the membersApi instance with a newly
configured one.

* Set the membersApiInstance logger to use common.logging

refs https://github.com/TryGhost/Members/issues/38

Passes the Ghost logger to the members api, so that we can keep an eye
on errors produced by the api.

* Refactored memberService use to always use getter

refs https://github.com/TryGhost/Members/issues/38

This will allow us to switch out the membersApi and the consumers of it
to have the updated reference by going through a getter.

* Installed @tryghost/members-api@0.3.0

refs https://github.com/TryGhost/Members/issues/38

Adds support for setting the logger

* Uninstalled stripe@7.0.0

refs https://github.com/TryGhost/Members/issues/38

The stripe module is now a dep of members-api, as it should be

* Updated members service to reconfigure settings

refs https://github.com/TryGhost/Members/issues/38

Previously we were unable to stop an invalidly configured members api
instance, now that we create a new instance, we can wait for the ready
or error event and only switch it out then.
2019-07-18 15:37:11 +08:00

213 lines
5.8 KiB
JavaScript

const url = require('url');
const settingsCache = require('../settings/cache');
const urlUtils = require('../../lib/url-utils');
const MembersApi = require('@tryghost/members-api');
const common = require('../../lib/common');
const models = require('../../models');
const mail = require('../mail');
const blogIcon = require('../../lib/image/blog-icon');
const doBlock = fn => fn();
function createMember({name, email, password}) {
return models.Member.add({
name,
email,
password
}).then((member) => {
return member.toJSON();
});
}
function updateMember(member, newData) {
return models.Member.findOne(member, {
require: true
}).then(({id}) => {
return models.Member.edit(newData, {id});
}).then((member) => {
return member.toJSON();
});
}
function getMember(data, options = {}) {
return models.Member.findOne(data, Object.assign({require: true}, options)).then((model) => {
if (!model) {
return null;
}
return model.toJSON(options);
});
}
function deleteMember(options) {
options = options || {};
return models.Member.destroy(options).catch(models.Member.NotFoundError, () => {
throw new common.errors.NotFoundError({
message: common.i18n.t('errors.api.resource.resourceNotFound', {
resource: 'Member'
})
});
});
}
function listMembers(options) {
return models.Member.findPage(options).then((models) => {
return {
members: models.data.map(model => model.toJSON(options)),
meta: models.meta
};
});
}
function validateMember({email, password}) {
return models.Member.findOne({email}, {
require: true
}).then((member) => {
return member.comparePassword(password).then((res) => {
if (!res) {
throw new Error('Password is incorrect');
}
return member;
});
}).then((member) => {
return member.toJSON();
});
}
function getSubscriptionSettings() {
let membersSettings = settingsCache.get('members_subscription_settings');
if (!membersSettings) {
membersSettings = {
isPaid: false,
paymentProcessors: [{
adapter: 'stripe',
config: {
secret_token: '',
public_token: '',
product: {
name: 'Ghost Subscription'
},
plans: [
{
name: 'Monthly',
currency: 'usd',
interval: 'month',
amount: ''
},
{
name: 'Yearly',
currency: 'usd',
interval: 'year',
amount: ''
}
]
}
}]
};
}
if (!membersSettings.isPaid) {
membersSettings.paymentProcessors = [];
}
return membersSettings;
}
const siteUrl = urlUtils.getSiteUrl();
const siteOrigin = doBlock(() => {
const {protocol, host} = url.parse(siteUrl);
return `${protocol}//${host}`;
});
const adminOrigin = doBlock(() => {
const {protocol, host} = url.parse(urlUtils.urlFor('admin', true));
return `${protocol}//${host}`;
});
const getApiUrl = ({version, type}) => {
const {href} = new url.URL(
urlUtils.getApiPath({version, type}),
urlUtils.urlFor('admin', true)
);
return href;
};
const contentApiUrl = getApiUrl({version: 'v2', type: 'content'});
const membersApiUrl = getApiUrl({version: 'v2', type: 'members'});
const accessControl = {
[siteOrigin]: {
[contentApiUrl]: {
tokenLength: '20m'
},
[membersApiUrl]: {
tokenLength: '180d'
}
},
'*': {
tokenLength: '20m'
}
};
const sendEmail = (function createSendEmail(mailer) {
return function sendEmail(member, {token}) {
if (!(mailer instanceof mail.GhostMailer)) {
mailer = new mail.GhostMailer();
}
const message = {
to: member.email,
subject: 'Reset password',
html: `
Hi ${member.name},
To reset your password, click the following link and follow the instructions:
${siteUrl}#reset-password?token=${token}
If you didn't request a password change, just ignore this email.
`
};
/* eslint-disable */
// @TODO remove this
console.log(message.html);
/* eslint-enable */
return mailer.send(message).catch((err) => {
return Promise.reject(err);
});
};
})();
const getSiteConfig = () => {
return {
title: settingsCache.get('title') ? settingsCache.get('title').replace(/"/g, '\\"') : 'Publication',
icon: blogIcon.getIconUrl()
};
};
module.exports = createApiInstance;
function createApiInstance() {
const membersApiInstance = MembersApi({
authConfig: {
issuer: membersApiUrl,
ssoOrigin: adminOrigin,
publicKey: settingsCache.get('members_public_key'),
privateKey: settingsCache.get('members_private_key'),
sessionSecret: settingsCache.get('members_session_secret'),
accessControl
},
paymentConfig: {
processors: getSubscriptionSettings().paymentProcessors
},
siteConfig: getSiteConfig(),
createMember,
getMember,
deleteMember,
listMembers,
validateMember,
updateMember,
sendEmail
});
membersApiInstance.setLogger(common.logging);
return membersApiInstance;
}