Ghost/core/server/services/members/middleware.js

209 lines
7.7 KiB
JavaScript
Raw Normal View History

const _ = require('lodash');
const logging = require('../../../shared/logging');
const membersService = require('./index');
const urlUtils = require('../../../shared/url-utils');
const ghostVersion = require('../../lib/ghost-version');
const settingsCache = require('../settings/cache');
const {formattedMemberResponse} = require('./utils');
// @TODO: This piece of middleware actually belongs to the frontend, not to the member app
// Need to figure a way to separate these things (e.g. frontend actually talks to members API)
const loadMemberSession = async function (req, res, next) {
try {
const member = await membersService.ssr.getMemberDataFromSession(req, res);
Object.assign(req, {member});
res.locals.member = req.member;
next();
} catch (err) {
Object.assign(req, {member: null});
next();
}
};
const getIdentityToken = async function (req, res) {
try {
const token = await membersService.ssr.getIdentityTokenForMemberFromSession(req, res);
res.writeHead(200);
res.end(token);
} catch (err) {
res.writeHead(err.statusCode);
res.end(err.message);
}
};
const deleteSession = async function (req, res) {
try {
await membersService.ssr.deleteSession(req, res);
res.writeHead(204);
res.end();
} catch (err) {
res.writeHead(err.statusCode);
res.end(err.message);
}
};
const getMemberData = async function (req, res) {
try {
const member = await membersService.ssr.getMemberDataFromSession(req, res);
if (member) {
res.json(formattedMemberResponse(member));
} else {
res.json(null);
}
} catch (err) {
res.writeHead(204);
res.end();
}
};
const updateMemberData = async function (req, res) {
try {
const data = _.pick(req.body, 'name', 'subscribed');
const member = await membersService.ssr.getMemberDataFromSession(req, res);
if (member) {
const options = {
id: member.id,
withRelated: ['stripeSubscriptions', 'stripeSubscriptions.customer']
};
const updatedMember = await membersService.api.members.update(data, options);
res.json(formattedMemberResponse(updatedMember.toJSON()));
} else {
res.json(null);
}
} catch (err) {
res.writeHead(err.statusCode);
res.end(err.message);
}
};
const getDefaultProductPrices = async function () {
const page = await membersService.api.productRepository.list({
limit: 1
});
const [product] = page.data;
if (product) {
const model = await membersService.api.productRepository.get({id: product.get('id')}, {withRelated: ['stripePrices']});
const productData = model.toJSON();
const prices = productData.stripePrices || [];
const activePrices = prices.filter((d) => {
return !!d.active;
});
const monthlyPriceId = settingsCache.get('members_monthly_price_id');
const yearlyPriceId = settingsCache.get('members_yearly_price_id');
const filteredPrices = activePrices.filter((d) => {
return [monthlyPriceId, yearlyPriceId].includes(d.id);
});
return {
product: productData,
prices: filteredPrices
};
}
return {};
};
const getMemberSiteData = async function (req, res) {
const isStripeConfigured = membersService.config.isStripeConnected();
const domain = urlUtils.urlFor('home', true).match(new RegExp('^https?://([^/:?#]+)(?:[/:?#]|$)', 'i'));
const firstpromoterId = settingsCache.get('firstpromoter') ? settingsCache.get('firstpromoter_id') : '';
const blogDomain = domain && domain[1];
let supportAddress = settingsCache.get('members_support_address') || 'noreply';
if (!supportAddress.includes('@')) {
supportAddress = `${supportAddress}@${blogDomain}`;
}
const {product = {}, prices = []} = await getDefaultProductPrices() || {};
const response = {
title: settingsCache.get('title'),
description: settingsCache.get('description'),
logo: settingsCache.get('logo'),
icon: settingsCache.get('icon'),
accent_color: settingsCache.get('accent_color'),
url: urlUtils.urlFor('home', true),
version: ghostVersion.safe,
plans: membersService.config.getPublicPlans(),
prices,
product: {
name: product.name || '',
description: product.description || ''
},
free_price_name: settingsCache.get('members_free_price_name'),
free_price_description: settingsCache.get('members_free_price_description'),
allow_self_signup: membersService.config.getAllowSelfSignup(),
members_signup_access: settingsCache.get('members_signup_access'),
is_stripe_configured: isStripeConfigured,
portal_button: settingsCache.get('portal_button'),
portal_name: settingsCache.get('portal_name'),
portal_plans: settingsCache.get('portal_plans'),
portal_button_icon: settingsCache.get('portal_button_icon'),
portal_button_signup_text: settingsCache.get('portal_button_signup_text'),
portal_button_style: settingsCache.get('portal_button_style'),
firstpromoter_id: firstpromoterId,
members_support_address: supportAddress
};
res.json({site: response});
};
const createSessionFromMagicLink = async function (req, res, next) {
if (!req.url.includes('token=')) {
return next();
}
// req.query is a plain object, copy it to a URLSearchParams object so we can call toString()
const searchParams = new URLSearchParams('');
Object.keys(req.query).forEach((param) => {
// don't copy the token param
if (param !== 'token') {
searchParams.set(param, req.query[param]);
}
});
try {
const member = await membersService.ssr.exchangeTokenForSession(req, res);
const subscriptions = member && member.subscriptions || [];
const action = req.query.action;
if (action === 'signup') {
let customRedirect = '';
if (subscriptions.find(sub => ['active', 'trialing'].includes(sub.status))) {
customRedirect = settingsCache.get('members_paid_signup_redirect') || '';
} else {
customRedirect = settingsCache.get('members_free_signup_redirect') || '';
}
if (customRedirect && customRedirect !== '/') {
const baseUrl = urlUtils.getSiteUrl();
const ensureEndsWith = (string, endsWith) => (string.endsWith(endsWith) ? string : string + endsWith);
const removeLeadingSlash = string => string.replace(/^\//, '');
const redirectUrl = new URL(removeLeadingSlash(ensureEndsWith(customRedirect, '/')), ensureEndsWith(baseUrl, '/'));
return res.redirect(redirectUrl.href);
}
}
// Do a standard 302 redirect to the homepage, with success=true
searchParams.set('success', true);
res.redirect(`${urlUtils.getSubdir()}/?${searchParams.toString()}`);
} catch (err) {
logging.warn(err.message);
// Do a standard 302 redirect to the homepage, with success=false
searchParams.set('success', false);
res.redirect(`${urlUtils.getSubdir()}/?${searchParams.toString()}`);
}
};
// Set req.member & res.locals.member if a cookie is set
module.exports = {
loadMemberSession,
createSessionFromMagicLink,
getIdentityToken,
getMemberData,
updateMemberData,
getMemberSiteData,
deleteSession,
stripeWebhooks: (req, res, next) => membersService.api.middleware.handleStripeWebhook(req, res, next)
};