mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-21 01:41:46 +03:00
f9899cb8c4
* Removed support for cookies in members auth middleware no-issue The members middleware will no longer be supporting cookies, the cookie will be handled by a new middleware specific for serverside rendering, more informations can be found here: https://paper.dropbox.com/doc/Members-Auth-II-4WP4vF6coMqDYbSMIajo5 * Removed members auth middleware from site app no-issue The site app no longer needs the members auth middleware as it doesn't support cookies, and will be replaced by ssr specific middleware. https://paper.dropbox.com/doc/Members-Auth-II-4WP4vF6coMqDYbSMIajo5 * Added comment for session_secret setting no-issue We are going to have multiple concepts of sessions, so adding a comment here to be specific that this is for the Ghost Admin client * Added theme_session_secret setting dynamic default no-issue Sessions for the theme layer will be signed, so we generate a random hex string to use as a signing key * Added getPublicConfig method * Replaced export of httpHandler with POJO apiInstance no-issue This is mainly to reduce the public api, so it's easier to document. * Renamed memberUserObject -> members no-issue Simplifies the interface, and is more inline with what we would want to export as an api library. * Removed use of require options inside members no-issue This was too tight of a coupling between Ghost and Members * Simplified apiInstance definition no-issue * Added getMember method to members api * Added MembersSSR instance to members service * Wired up routes for members ssr * Updated members auth middleware to use getPublicConfig * Removed publicKey static export from members service * Used real session secret no-issue * Added DELETE /members/ssr handler no-issue This allows users to log out of the theme layer * Fixed missing code property no-issue Ignition uses the statusCode property to forward status codes to call sites * Removed superfluous error middleware no-issue Before we used generic JWT middleware which would reject, now the middleware catches it's own error and doesn't error, thus this middleware is unecessary. * Removed console.logs no-issue * Updated token expirty to hardcoded 20 minutes no-issue This returns to our previous state of using short lived tokens, both for security and simplicity. * Removed hardcoded default member settings no-issue This is no longer needed, as defaults are in default-settings.json * Removed stripe from default payment processor no-issue * Exported `getSiteUrl` method from url utils no-issue This keeps inline with newer naming conventions * Updated how audience access control works no-issue Rather than being passed a function, members api now receives an object which describes which origins have access to which audiences, and how long those tokens should be allowed to work for. It also allows syntax for default tokens where audience === origin requesting it. This can be set to undefined or null to disable this functionality. { "http://site.com": { "http://site.com": { tokenLength: '5m' }, "http://othersite.com": { tokenLength: '1h' } }, "*": { tokenLength: '30m' } } * Updated members service to use access control feature no-issue This also cleans up a lot of unecessary variable definitions, and some other minor cleanups. * Added status code to auth pages html response no-issue This was missing, probably default but better to be explicit * Updated gateway to have membersApiUrl from config no-issue Previously we were parsing the url, this was not very safe as we can have Ghost hosted on a subdomain, and this would have failed. * Added issuer to public config for members no-issue This can be used to request SSR tokens in the client * Fixed path for gateway bundle no-issue * Updated settings model tests no-issue * Revert "Removed stripe from default payment processor" This reverts commit 1d88d9b6d73a10091070bcc1b7f5779d071c7845. * Revert "Removed hardcoded default member settings" This reverts commit 9d899048ba7d4b272b9ac65a95a52af66b30914a. * Installed @tryghost/members-ssr * Fixed tests for settings model
227 lines
6.3 KiB
JavaScript
227 lines
6.3 KiB
JavaScript
const url = require('url');
|
|
const settingsCache = require('../settings/cache');
|
|
const urlService = require('../url');
|
|
const MembersApi = require('../../lib/members');
|
|
const MembersSSR = require('members-ssr');
|
|
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 = urlService.utils.getSiteUrl();
|
|
const siteOrigin = doBlock(() => {
|
|
const {protocol, host} = url.parse(siteUrl);
|
|
return `${protocol}//${host}`;
|
|
});
|
|
|
|
const getApiUrl = ({version, type}) => {
|
|
const {href} = new url.URL(
|
|
urlService.utils.getApiPath({version, type}),
|
|
siteUrl
|
|
);
|
|
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 {
|
|
defaultBlogTitle: settingsCache.get('title') ? settingsCache.get('title').replace(/"/g, '\\"') : 'Publication',
|
|
blogIconUrl: blogIcon.getIconUrl()
|
|
};
|
|
};
|
|
|
|
const membersApiInstance = MembersApi({
|
|
authConfig: {
|
|
issuer: membersApiUrl,
|
|
ssoOrigin: siteOrigin,
|
|
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
|
|
});
|
|
|
|
const updateSettingFromModel = function updateSettingFromModel(settingModel) {
|
|
if (!['members_subscription_settings', 'title', 'icon'].includes(settingModel.get('key'))) {
|
|
return;
|
|
}
|
|
|
|
membersApiInstance.reconfigureSettings({
|
|
paymentConfig: {
|
|
processors: getSubscriptionSettings().paymentProcessors
|
|
},
|
|
siteConfig: getSiteConfig()
|
|
});
|
|
};
|
|
|
|
// Bind to events to automatically keep subscription info up-to-date from settings
|
|
common.events.on('settings.edited', updateSettingFromModel);
|
|
|
|
module.exports = membersApiInstance;
|
|
module.exports.ssr = MembersSSR({
|
|
cookieSecure: urlService.utils.isSSL(siteUrl),
|
|
cookieKeys: [settingsCache.get('theme_session_secret')],
|
|
membersApi: membersApiInstance
|
|
});
|
|
module.exports.isPaymentConfigured = function () {
|
|
return getSubscriptionSettings().paymentProcessors.length !== 0;
|
|
};
|