diff --git a/core/server/api/v2/members.js b/core/server/api/v2/members.js index 08e931ed70..7a16817aa0 100644 --- a/core/server/api/v2/members.js +++ b/core/server/api/v2/members.js @@ -1,4 +1,6 @@ -const memberUserObject = require('../../services/members').api.members; +// NOTE: We must not cache references to membersService.api +// as it is a getter and may change during runtime. +const membersService = require('../../services/members'); const members = { docName: 'members', @@ -14,7 +16,7 @@ const members = { permissions: true, validation: {}, query(frame) { - return memberUserObject.list(frame.options); + return membersService.api.members.list(frame.options); } }, @@ -27,7 +29,7 @@ const members = { validation: {}, permissions: true, query(frame) { - return memberUserObject.get(frame.data, frame.options); + return membersService.api.members.get(frame.data, frame.options); } }, @@ -47,7 +49,7 @@ const members = { permissions: true, query(frame) { frame.options.require = true; - return memberUserObject.destroy(frame.options).return(null); + return membersService.api.members.destroy(frame.options).return(null); } } }; diff --git a/core/server/api/v2/utils/serializers/output/utils/members.js b/core/server/api/v2/utils/serializers/output/utils/members.js index edcf2b682f..880928015a 100644 --- a/core/server/api/v2/utils/serializers/output/utils/members.js +++ b/core/server/api/v2/utils/serializers/output/utils/members.js @@ -23,7 +23,7 @@ function hideMembersOnlyContent(attrs, frame) { } const memberHasPlan = !!(frame.original.context.member.plans || []).length; - if (!membersService.api.isPaymentConfigured()) { + if (!membersService.isPaymentConfigured()) { return PERMIT_CONTENT; } if (memberHasPlan) { diff --git a/core/server/services/members/api.js b/core/server/services/members/api.js index 57e9254f6b..fc47603d46 100644 --- a/core/server/services/members/api.js +++ b/core/server/services/members/api.js @@ -2,7 +2,6 @@ const url = require('url'); const settingsCache = require('../settings/cache'); const urlUtils = require('../../lib/url-utils'); const MembersApi = require('@tryghost/members-api'); -const MembersSSR = require('@tryghost/members-ssr'); const common = require('../../lib/common'); const models = require('../../models'); const mail = require('../mail'); @@ -182,50 +181,32 @@ const getSiteConfig = () => { }; }; -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 -}); +module.exports = createApiInstance; -const updateSettingFromModel = function updateSettingFromModel(settingModel) { - if (!['members_subscription_settings', 'title', 'icon'].includes(settingModel.get('key'))) { - return; - } - - membersApiInstance.reconfigureSettings({ +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() + siteConfig: getSiteConfig(), + createMember, + getMember, + deleteMember, + listMembers, + validateMember, + updateMember, + sendEmail }); -}; -// Bind to events to automatically keep subscription info up-to-date from settings -common.events.on('settings.edited', updateSettingFromModel); + membersApiInstance.setLogger(common.logging); -module.exports = membersApiInstance; -module.exports.ssr = MembersSSR({ - cookieSecure: urlUtils.isSSL(siteUrl), - cookieKeys: [settingsCache.get('theme_session_secret')], - membersApi: membersApiInstance -}); -module.exports.isPaymentConfigured = function () { - return getSubscriptionSettings().paymentProcessors.length !== 0; -}; + return membersApiInstance; +} diff --git a/core/server/services/members/authPages.js b/core/server/services/members/authPages.js deleted file mode 100644 index 5d8c786eb8..0000000000 --- a/core/server/services/members/authPages.js +++ /dev/null @@ -1,9 +0,0 @@ -const {static} = require('express'); -const path = require('path'); - -module.exports = static( - path.join( - require.resolve('@tryghost/members-auth-pages'), - '../dist' - ) -); diff --git a/core/server/services/members/index.js b/core/server/services/members/index.js index 2b3e4c1949..c81b9d905b 100644 --- a/core/server/services/members/index.js +++ b/core/server/services/members/index.js @@ -1,9 +1,60 @@ -module.exports = { - get api() { - return require('./api'); +const {static} = require('express'); +const path = require('path'); +const MembersSSR = require('@tryghost/members-ssr'); + +const createMembersApiInstance = require('./api'); +const common = require('../../lib/common'); +const urlUtils = require('../../lib/url-utils'); +const settingsCache = require('../settings/cache'); + +let membersApi; + +// Bind to events to automatically keep subscription info up-to-date from settings +common.events.on('settings.edited', function updateSettingFromModel(settingModel) { + if (!['members_subscription_settings', 'title', 'icon'].includes(settingModel.get('key'))) { + return; + } + + const reconfiguredMembersAPI = createMembersApiInstance(); + reconfiguredMembersAPI.bus.on('ready', function () { + membersApi = reconfiguredMembersAPI; + }); + reconfiguredMembersAPI.bus.on('error', function (err) { + common.logging.error(err); + }); +}); + +const membersService = { + isPaymentConfigured() { + const settings = settingsCache.get('members_subscription_settings'); + return !!settings && settings.isPaid && settings.paymentProcessors.length !== 0; }, - get authPages() { - return require('./authPages'); - } + get api() { + if (!membersApi) { + membersApi = createMembersApiInstance(); + + membersApi.bus.on('error', function (err) { + common.logging.error(err); + }); + } + return membersApi; + }, + + ssr: MembersSSR({ + cookieSecure: urlUtils.isSSL(urlUtils.getSiteUrl()), + cookieKeys: [settingsCache.get('theme_session_secret')], + // This is passed as a function so that updates to the instance + // are picked up in the ssr module + membersApi: () => membersApi + }), + + authPages: static( + path.join( + require.resolve('@tryghost/members-auth-pages'), + '../dist' + ) + ) }; + +module.exports = membersService; diff --git a/core/server/web/api/v2/members/app.js b/core/server/web/api/v2/members/app.js index 5e5506215a..45a1cfb4ea 100644 --- a/core/server/web/api/v2/members/app.js +++ b/core/server/web/api/v2/members/app.js @@ -15,7 +15,8 @@ module.exports = function setupMembersApiApp() { apiApp.use('/static/auth', membersService.authPages); // Set up the api endpoints and the gateway - apiApp.use(membersService.api); + // NOTE: this is wrapped in a function to ensure we always go via the getter + apiApp.use((req, res, next) => membersService.api(req, res, next)); // API error handling apiApp.use(shared.middlewares.errorHandler.resourceNotFound); diff --git a/core/server/web/site/app.js b/core/server/web/site/app.js index ffe91a6672..c2f54439b3 100644 --- a/core/server/web/site/app.js +++ b/core/server/web/site/app.js @@ -93,7 +93,7 @@ module.exports = function setupSiteApp(options = {}) { // @TODO only loads this stuff if members is enabled // Set req.member & res.locals.member if a cookie is set siteApp.post('/members/ssr', shared.middlewares.labs.members, function (req, res) { - membersService.api.ssr.exchangeTokenForSession(req, res).then(() => { + membersService.ssr.exchangeTokenForSession(req, res).then(() => { res.writeHead(200); res.end(); }).catch((err) => { @@ -102,7 +102,7 @@ module.exports = function setupSiteApp(options = {}) { }); }); siteApp.delete('/members/ssr', shared.middlewares.labs.members, function (req, res) { - membersService.api.ssr.deleteSession(req, res).then(() => { + membersService.ssr.deleteSession(req, res).then(() => { res.writeHead(204); res.end(); }).catch((err) => { @@ -111,7 +111,7 @@ module.exports = function setupSiteApp(options = {}) { }); }); siteApp.use(function (req, res, next) { - membersService.api.ssr.getMemberDataFromSession(req, res).then((member) => { + membersService.ssr.getMemberDataFromSession(req, res).then((member) => { req.member = member; next(); }).catch(() => { diff --git a/package.json b/package.json index a82c0490eb..9ce394c657 100644 --- a/package.json +++ b/package.json @@ -41,9 +41,9 @@ "dependencies": { "@nexes/nql": "0.2.2", "@tryghost/helpers": "1.1.6", - "@tryghost/members-api": "0.2.0", + "@tryghost/members-api": "0.3.0", "@tryghost/members-auth-pages": "1.1.0", - "@tryghost/members-ssr": "0.1.5", + "@tryghost/members-ssr": "0.2.1", "@tryghost/members-theme-bindings": "0.2.3", "@tryghost/social-urls": "0.1.0", "@tryghost/string": "^0.1.3", @@ -118,7 +118,6 @@ "semver": "6.2.0", "simple-dom": "0.3.2", "simple-html-tokenizer": "0.5.7", - "stripe": "^7.0.0", "uuid": "3.3.2", "validator": "6.3.0", "xml": "1.0.1" diff --git a/yarn.lock b/yarn.lock index 03eec6f412..7285e8ba41 100644 --- a/yarn.lock +++ b/yarn.lock @@ -149,10 +149,10 @@ dependencies: "@tryghost/kg-clean-basic-html" "^0.1.1" -"@tryghost/members-api@0.2.0": - version "0.2.0" - resolved "https://registry.yarnpkg.com/@tryghost/members-api/-/members-api-0.2.0.tgz#1f13dabc39015f8a5447a714dc05162426775027" - integrity sha512-fLTQylyUz16ZL0KJ4kBvWnUsQbq1NkLFXVkQjOEo/xFV1SG0iJBKlkVtPWfyncSHyGBSXAv9TcQymiT+9vr7pQ== +"@tryghost/members-api@0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@tryghost/members-api/-/members-api-0.3.0.tgz#b1fe4c4853237c1af95065f5db2bfb2072a87524" + integrity sha512-YtYjQwFGrN6uPuwWB8x1WqSuLe8c2m5wuPsyBpKXcV69yEqFz4QCyLMoYs4c6Y4lnfUf1lZAwnQSuDgczT46XQ== dependencies: bluebird "^3.5.4" body-parser "^1.19.0" @@ -162,6 +162,7 @@ jsonwebtoken "^8.5.1" lodash "^4.17.11" node-jose "^1.1.3" + stripe "^7.4.0" "@tryghost/members-auth-pages@1.1.0": version "1.1.0" @@ -203,10 +204,10 @@ ghost-ignition "^3.1.0" lodash "^4.17.11" -"@tryghost/members-ssr@0.1.5": - version "0.1.5" - resolved "https://registry.yarnpkg.com/@tryghost/members-ssr/-/members-ssr-0.1.5.tgz#531a36233ac4d7e23b2582ec2e48698aba739c00" - integrity sha512-nhceqbnoSZwJJN2xdEBPfRZu2fCDMuDnBLA84h+k8+RGoU+8qxzHDCLR1vgOlt8vVWUAP8HRS1/C4GyhuaUNXQ== +"@tryghost/members-ssr@0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@tryghost/members-ssr/-/members-ssr-0.2.1.tgz#156846832caeeaaa831206522dcc1e5b94e1335b" + integrity sha512-pr3IYDymyDm++428LNJn3cfhZmW8j2Qtr/AmyPpmRIDSmpzbEk5wl28Dc+jWpH6ij36DbOwwfVUNZ9SW1Lwi3w== dependencies: bluebird "^3.5.3" concat-stream "^2.0.0" @@ -7413,7 +7414,7 @@ strip-json-comments@2.0.1, strip-json-comments@^2.0.1, strip-json-comments@~2.0. version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" -stripe@^7.0.0: +stripe@^7.4.0: version "7.4.0" resolved "https://registry.yarnpkg.com/stripe/-/stripe-7.4.0.tgz#d40834f7763ebe775fe944db94bd3f31f291b0fc" integrity sha512-eurSZJw45MvnV7PjmFHMgJMkCihHgqGHr11OHpFdMh+5CCyYvbVlA5uP5VoVQakhYjSLCObs0dbXtGYhIAMKvw==