UNPKG

ghost

Version:

The professional publishing platform

152 lines (126 loc) 6.19 kB
const debug = require('@tryghost/debug')('members'); const cors = require('cors'); const bodyParser = require('body-parser'); const express = require('../../../shared/express'); const sentry = require('../../../shared/sentry'); const membersService = require('../../services/members'); const stripeService = require('../../services/stripe'); const middleware = membersService.middleware; const shared = require('../shared'); const labs = require('../../../shared/labs'); const errorHandler = require('@tryghost/mw-error-handler'); const config = require('../../../shared/config'); const {http} = require('@tryghost/api-framework'); const api = require('../../api').endpoints; const commentRouter = require('../comments'); const announcementRouter = require('../announcement'); /** * @returns {import('express').Application} */ module.exports = function setupMembersApp() { debug('Members App setup start'); const membersApp = express('members'); // Members API shouldn't be cached membersApp.use(shared.middleware.cacheControl('private')); // Support CORS for requests from the frontend membersApp.use(cors({maxAge: config.get('caching:cors:maxAge')})); // Currently global handling for signing in with ?token= magiclinks membersApp.use(middleware.createSessionFromMagicLink); // Routing // Webhooks membersApp.post('/webhooks/stripe', bodyParser.raw({type: 'application/json'}), stripeService.webhookController.handle.bind(stripeService.webhookController)); // Initializes members specific routes as well as assigns members specific data to the req/res objects // We don't want to add global bodyParser middleware as that interferes with stripe webhook requests on - `/webhooks`. // Manage newsletter subscription via unsubscribe link - these should be authenticated by uuid and hashed key membersApp.get('/api/member/newsletters', middleware.authMemberByUuid, middleware.getMemberNewsletters ); membersApp.put('/api/member/newsletters', bodyParser.json({limit: '50mb'}), middleware.authMemberByUuid, middleware.updateMemberNewsletters ); // Get and update member data // Caching members content is an experimental feature const shouldCacheMembersContent = config.get('cacheMembersContent:enabled'); if (shouldCacheMembersContent) { membersApp.get('/api/member', middleware.loadMemberSession, middleware.accessInfoSession, middleware.getMemberData); } else { membersApp.get('/api/member', middleware.getMemberData); } membersApp.put('/api/member', bodyParser.json({limit: '50mb'}), middleware.updateMemberData); membersApp.post('/api/member/email', bodyParser.json({limit: '50mb'}), (req, res, next) => membersService.api.middleware.updateEmailAddress(req, res, next)); // Remove email from suppression list membersApp.delete('/api/member/suppression', middleware.deleteSuppression); // Manage session membersApp.get('/api/session', middleware.getIdentityToken); membersApp.delete('/api/session', bodyParser.json({limit: '5mb'}), middleware.deleteSession); membersApp.get('/api/integrity-token', middleware.createIntegrityToken); // NOTE: this is wrapped in a function to ensure we always go via the getter membersApp.post( '/api/send-magic-link', bodyParser.json(), middleware.verifyIntegrityToken, // Prevent brute forcing email addresses (user enumeration) shared.middleware.brute.membersAuthEnumeration, // Prevent brute forcing passwords for the same email address shared.middleware.brute.membersAuth, function lazySendMagicLinkMw(req, res, next) { return membersService.api.middleware.sendMagicLink(req, res, next); } ); membersApp.post('/api/create-stripe-checkout-session', function lazyCreateCheckoutSessionMw(req, res, next) { return membersService.api.middleware.createCheckoutSession(req, res, next); }); membersApp.post('/api/create-stripe-update-session', function lazyCreateCheckoutSetupSessionMw(req, res, next) { return membersService.api.middleware.createCheckoutSetupSession(req, res, next); }); membersApp.put('/api/subscriptions/:id', function lazyUpdateSubscriptionMw(req, res, next) { return membersService.api.middleware.updateSubscription(req, res, next); }); // Comments membersApp.use('/api/comments', commentRouter()); // Feedback membersApp.post( '/api/feedback', labs.enabledMiddleware('audienceFeedback'), bodyParser.json({limit: '50mb'}), middleware.loadMemberSession, middleware.authMemberByUuid, http(api.feedbackMembers.add) ); // Announcement membersApp.use( '/api/announcement', labs.enabledMiddleware('announcementBar'), middleware.loadMemberSession, announcementRouter() ); // Recommendations membersApp.post( '/api/recommendations/:id/clicked', middleware.loadMemberSession, http(api.recommendationsPublic.trackClicked) ); // Recommendations membersApp.post( '/api/recommendations/:id/subscribed', middleware.loadMemberSession, http(api.recommendationsPublic.trackSubscribed) ); // Allow external systems to read public settings via the members api // Without CORS issues and without a required integration token // 1. Detect if a site is Running Ghost // 2. For recommendations to know when we can offer 'one-click-subscribe' to know if members are enabled // Why not content API? Domain can be different from recommended domain + CORS issues membersApp.get('/api/site', http(api.site.read)); // API error handling membersApp.use('/api', errorHandler.resourceNotFound); membersApp.use('/api', errorHandler.handleJSONResponse(sentry)); // Webhook error handling membersApp.use('/webhooks', errorHandler.resourceNotFound); membersApp.use('/webhooks', errorHandler.handleJSONResponse(sentry)); debug('Members App setup end'); return membersApp; };