mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-27 21:03:29 +03:00
f3130d9538
refs https://github.com/TryGhost/Team/issues/1174 This paves the way for Ghost to be able to redirect to the referrer page when dealign with signup magic links. We pass the referrer for all types of magic links however, to allow extension of this functionality in the future. We've also removed the concept of `requestSrc` which has been unused for a while now.
315 lines
9.2 KiB
JavaScript
315 lines
9.2 KiB
JavaScript
const {Router} = require('express');
|
|
const body = require('body-parser');
|
|
const MagicLink = require('@tryghost/magic-link');
|
|
const errors = require('@tryghost/errors');
|
|
|
|
const MemberAnalyticsService = require('@tryghost/member-analytics-service');
|
|
const MembersAnalyticsIngress = require('@tryghost/members-analytics-ingress');
|
|
const PaymentsService = require('@tryghost/members-payments');
|
|
|
|
const TokenService = require('./services/token');
|
|
const GeolocationSerice = require('./services/geolocation');
|
|
const MemberBREADService = require('./services/member-bread');
|
|
const MemberRepository = require('./repositories/member');
|
|
const EventRepository = require('./repositories/event');
|
|
const ProductRepository = require('./repositories/product');
|
|
const RouterController = require('./controllers/router');
|
|
const MemberController = require('./controllers/member');
|
|
const WellKnownController = require('./controllers/well-known');
|
|
|
|
module.exports = function MembersAPI({
|
|
tokenConfig: {
|
|
issuer,
|
|
privateKey,
|
|
publicKey
|
|
},
|
|
auth: {
|
|
allowSelfSignup = () => true,
|
|
getSigninURL,
|
|
tokenProvider
|
|
},
|
|
mail: {
|
|
transporter,
|
|
getText,
|
|
getHTML,
|
|
getSubject
|
|
},
|
|
models: {
|
|
EmailRecipient,
|
|
StripeCustomer,
|
|
StripeCustomerSubscription,
|
|
Member,
|
|
MemberCancelEvent,
|
|
MemberSubscribeEvent,
|
|
MemberLoginEvent,
|
|
MemberPaidSubscriptionEvent,
|
|
MemberPaymentEvent,
|
|
MemberStatusEvent,
|
|
MemberProductEvent,
|
|
MemberEmailChangeEvent,
|
|
MemberAnalyticEvent,
|
|
Offer,
|
|
OfferRedemption,
|
|
StripeProduct,
|
|
StripePrice,
|
|
Product,
|
|
Settings
|
|
},
|
|
stripeAPIService,
|
|
offersAPI,
|
|
labsService,
|
|
newslettersService
|
|
}) {
|
|
const tokenService = new TokenService({
|
|
privateKey,
|
|
publicKey,
|
|
issuer
|
|
});
|
|
|
|
const memberAnalyticsService = MemberAnalyticsService.create(MemberAnalyticEvent);
|
|
memberAnalyticsService.eventHandler.setupSubscribers();
|
|
|
|
const productRepository = new ProductRepository({
|
|
Product,
|
|
Settings,
|
|
StripeProduct,
|
|
StripePrice,
|
|
stripeAPIService
|
|
});
|
|
|
|
const memberRepository = new MemberRepository({
|
|
stripeAPIService,
|
|
tokenService,
|
|
newslettersService,
|
|
labsService,
|
|
productRepository,
|
|
Member,
|
|
MemberCancelEvent,
|
|
MemberSubscribeEvent,
|
|
MemberPaidSubscriptionEvent,
|
|
MemberEmailChangeEvent,
|
|
MemberStatusEvent,
|
|
MemberProductEvent,
|
|
OfferRedemption,
|
|
StripeCustomer,
|
|
StripeCustomerSubscription,
|
|
offerRepository: offersAPI.repository
|
|
});
|
|
|
|
const eventRepository = new EventRepository({
|
|
EmailRecipient,
|
|
MemberSubscribeEvent,
|
|
MemberPaidSubscriptionEvent,
|
|
MemberPaymentEvent,
|
|
MemberStatusEvent,
|
|
MemberLoginEvent,
|
|
labsService
|
|
});
|
|
|
|
const memberBREADService = new MemberBREADService({
|
|
offersAPI,
|
|
memberRepository,
|
|
emailService: {
|
|
async sendEmailWithMagicLink({email, requestedType}) {
|
|
return sendEmailWithMagicLink({
|
|
email,
|
|
requestedType,
|
|
options: {
|
|
forceEmailType: true
|
|
}
|
|
});
|
|
}
|
|
},
|
|
labsService,
|
|
stripeService: stripeAPIService
|
|
});
|
|
|
|
const geolocationService = new GeolocationSerice();
|
|
|
|
const magicLinkService = new MagicLink({
|
|
transporter,
|
|
tokenProvider,
|
|
getSigninURL,
|
|
getText,
|
|
getHTML,
|
|
getSubject
|
|
});
|
|
|
|
const memberController = new MemberController({
|
|
memberRepository,
|
|
productRepository,
|
|
StripePrice,
|
|
tokenService,
|
|
sendEmailWithMagicLink
|
|
});
|
|
|
|
const paymentsService = new PaymentsService({
|
|
Offer,
|
|
offersAPI,
|
|
stripeAPIService
|
|
});
|
|
|
|
const routerController = new RouterController({
|
|
offersAPI,
|
|
paymentsService,
|
|
productRepository,
|
|
memberRepository,
|
|
StripePrice,
|
|
allowSelfSignup,
|
|
magicLinkService,
|
|
stripeAPIService,
|
|
tokenService,
|
|
sendEmailWithMagicLink,
|
|
labsService
|
|
});
|
|
|
|
const wellKnownController = new WellKnownController({
|
|
tokenService
|
|
});
|
|
|
|
const users = memberRepository;
|
|
|
|
async function sendEmailWithMagicLink({email, requestedType, tokenData, options = {forceEmailType: false}, referrer = null}) {
|
|
let type = requestedType;
|
|
if (!options.forceEmailType) {
|
|
const member = await users.get({email});
|
|
if (member) {
|
|
type = 'signin';
|
|
} else if (type !== 'subscribe') {
|
|
type = 'signup';
|
|
}
|
|
}
|
|
return magicLinkService.sendMagicLink({email, type, tokenData: Object.assign({email}, tokenData), referrer});
|
|
}
|
|
|
|
function getMagicLink(email) {
|
|
return magicLinkService.getMagicLink({tokenData: {email}, type: 'signin'});
|
|
}
|
|
|
|
async function getMemberDataFromMagicLinkToken(token) {
|
|
const {email, labels = [], name = '', oldEmail, newsletters} = await magicLinkService.getDataFromToken(token);
|
|
if (!email) {
|
|
return null;
|
|
}
|
|
|
|
const member = oldEmail ? await getMemberIdentityData(oldEmail) : await getMemberIdentityData(email);
|
|
|
|
if (member) {
|
|
await MemberLoginEvent.add({member_id: member.id});
|
|
if (oldEmail) {
|
|
// user exists but wants to change their email address
|
|
await users.update({email}, {id: member.id});
|
|
return getMemberIdentityData(email);
|
|
}
|
|
return member;
|
|
}
|
|
const newMember = await users.create({name, email, labels, newsletters});
|
|
await MemberLoginEvent.add({member_id: newMember.id});
|
|
return getMemberIdentityData(email);
|
|
}
|
|
|
|
async function getMemberIdentityData(email) {
|
|
return memberBREADService.read({email});
|
|
}
|
|
|
|
async function getMemberIdentityToken(email) {
|
|
const member = await getMemberIdentityData(email);
|
|
if (!member) {
|
|
return null;
|
|
}
|
|
return tokenService.encodeIdentityToken({sub: member.email});
|
|
}
|
|
|
|
async function setMemberGeolocationFromIp(email, ip) {
|
|
if (!email || !ip) {
|
|
throw new errors.IncorrectUsageError({
|
|
message: 'setMemberGeolocationFromIp() expects email and ip arguments to be present'
|
|
});
|
|
}
|
|
|
|
// toJSON() is needed here otherwise users.update() will pick methods off
|
|
// the model object rather than data and fail to edit correctly
|
|
const member = (await users.get({email})).toJSON();
|
|
|
|
if (!member) {
|
|
throw new errors.NotFoundError({
|
|
message: `Member with email address ${email} does not exist`
|
|
});
|
|
}
|
|
|
|
// max request time is 500ms so shouldn't slow requests down too much
|
|
let geolocation = JSON.stringify(await geolocationService.getGeolocationFromIP(ip));
|
|
if (geolocation) {
|
|
await users.update({geolocation}, {id: member.id});
|
|
}
|
|
|
|
return getMemberIdentityData(email);
|
|
}
|
|
|
|
const forwardError = fn => async (req, res, next) => {
|
|
try {
|
|
await fn(req, res, next);
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
};
|
|
|
|
const middleware = {
|
|
sendMagicLink: Router().use(
|
|
body.json(),
|
|
forwardError((req, res) => routerController.sendMagicLink(req, res))
|
|
),
|
|
createCheckoutSession: Router().use(
|
|
body.json(),
|
|
forwardError((req, res) => routerController.createCheckoutSession(req, res))
|
|
),
|
|
createCheckoutSetupSession: Router().use(
|
|
body.json(),
|
|
forwardError((req, res) => routerController.createCheckoutSetupSession(req, res))
|
|
),
|
|
createEvents: Router().use(
|
|
body.json(),
|
|
forwardError((req, res) => MembersAnalyticsIngress.createEvents(req, res))
|
|
),
|
|
updateEmailAddress: Router().use(
|
|
body.json(),
|
|
forwardError((req, res) => memberController.updateEmailAddress(req, res))
|
|
),
|
|
updateSubscription: Router({mergeParams: true}).use(
|
|
body.json(),
|
|
forwardError((req, res) => memberController.updateSubscription(req, res))
|
|
),
|
|
wellKnown: Router()
|
|
.get('/jwks.json',
|
|
(req, res) => wellKnownController.getPublicKeys(req, res)
|
|
)
|
|
};
|
|
|
|
const getPublicConfig = function () {
|
|
return Promise.resolve({
|
|
publicKey,
|
|
issuer
|
|
});
|
|
};
|
|
|
|
const bus = new (require('events').EventEmitter)();
|
|
|
|
bus.emit('ready');
|
|
|
|
return {
|
|
middleware,
|
|
getMemberDataFromMagicLinkToken,
|
|
getMemberIdentityToken,
|
|
getMemberIdentityData,
|
|
setMemberGeolocationFromIp,
|
|
getPublicConfig,
|
|
bus,
|
|
sendEmailWithMagicLink,
|
|
getMagicLink,
|
|
members: users,
|
|
memberBREADService,
|
|
events: eventRepository,
|
|
productRepository
|
|
};
|
|
};
|