2021-07-14 15:32:09 +03:00
|
|
|
const {Router} = require('express');
|
|
|
|
const body = require('body-parser');
|
|
|
|
const MagicLink = require('@tryghost/magic-link');
|
2021-12-02 17:46:58 +03:00
|
|
|
const errors = require('@tryghost/errors');
|
2021-07-14 15:32:09 +03:00
|
|
|
|
2021-09-21 14:37:24 +03:00
|
|
|
const MemberAnalyticsService = require('@tryghost/member-analytics-service');
|
2021-09-22 14:21:03 +03:00
|
|
|
const MembersAnalyticsIngress = require('@tryghost/members-analytics-ingress');
|
2021-10-21 14:35:29 +03:00
|
|
|
const PaymentsService = require('@tryghost/members-payments');
|
2021-09-21 14:37:24 +03:00
|
|
|
|
2021-07-15 19:54:24 +03:00
|
|
|
const TokenService = require('./services/token');
|
|
|
|
const GeolocationSerice = require('./services/geolocation');
|
2021-08-25 14:30:49 +03:00
|
|
|
const MemberBREADService = require('./services/member-bread');
|
2021-07-15 19:54:24 +03:00
|
|
|
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');
|
2021-07-19 15:02:45 +03:00
|
|
|
const WellKnownController = require('./controllers/well-known');
|
2021-07-14 15:32:09 +03:00
|
|
|
|
|
|
|
module.exports = function MembersAPI({
|
|
|
|
tokenConfig: {
|
|
|
|
issuer,
|
|
|
|
privateKey,
|
|
|
|
publicKey
|
|
|
|
},
|
|
|
|
auth: {
|
2022-01-10 12:42:05 +03:00
|
|
|
allowSelfSignup = () => true,
|
2021-07-14 15:32:09 +03:00
|
|
|
getSigninURL,
|
|
|
|
tokenProvider
|
|
|
|
},
|
|
|
|
mail: {
|
|
|
|
transporter,
|
|
|
|
getText,
|
|
|
|
getHTML,
|
|
|
|
getSubject
|
|
|
|
},
|
|
|
|
models: {
|
2022-01-18 17:53:51 +03:00
|
|
|
EmailRecipient,
|
2021-07-14 15:32:09 +03:00
|
|
|
StripeCustomer,
|
|
|
|
StripeCustomerSubscription,
|
|
|
|
Member,
|
2022-03-12 00:44:28 +03:00
|
|
|
MemberCancelEvent,
|
2021-07-14 15:32:09 +03:00
|
|
|
MemberSubscribeEvent,
|
|
|
|
MemberLoginEvent,
|
|
|
|
MemberPaidSubscriptionEvent,
|
|
|
|
MemberPaymentEvent,
|
|
|
|
MemberStatusEvent,
|
2021-08-23 13:00:19 +03:00
|
|
|
MemberProductEvent,
|
2021-07-14 15:32:09 +03:00
|
|
|
MemberEmailChangeEvent,
|
2021-09-21 14:37:24 +03:00
|
|
|
MemberAnalyticEvent,
|
2021-10-20 15:40:34 +03:00
|
|
|
Offer,
|
2021-10-18 16:27:17 +03:00
|
|
|
OfferRedemption,
|
2021-07-14 15:32:09 +03:00
|
|
|
StripeProduct,
|
|
|
|
StripePrice,
|
2022-01-24 14:10:14 +03:00
|
|
|
Product,
|
|
|
|
Settings
|
2021-07-14 15:32:09 +03:00
|
|
|
},
|
2021-10-04 14:34:17 +03:00
|
|
|
stripeAPIService,
|
2021-10-13 12:11:12 +03:00
|
|
|
offersAPI,
|
2022-04-05 20:26:18 +03:00
|
|
|
labsService,
|
|
|
|
newslettersService
|
2021-07-14 15:32:09 +03:00
|
|
|
}) {
|
2021-09-17 12:25:57 +03:00
|
|
|
const tokenService = new TokenService({
|
|
|
|
privateKey,
|
|
|
|
publicKey,
|
|
|
|
issuer
|
|
|
|
});
|
|
|
|
|
2021-09-21 14:37:24 +03:00
|
|
|
const memberAnalyticsService = MemberAnalyticsService.create(MemberAnalyticEvent);
|
|
|
|
memberAnalyticsService.eventHandler.setupSubscribers();
|
|
|
|
|
2021-07-14 15:32:09 +03:00
|
|
|
const productRepository = new ProductRepository({
|
|
|
|
Product,
|
2022-01-24 14:10:14 +03:00
|
|
|
Settings,
|
2021-07-14 15:32:09 +03:00
|
|
|
StripeProduct,
|
|
|
|
StripePrice,
|
|
|
|
stripeAPIService
|
|
|
|
});
|
|
|
|
|
|
|
|
const memberRepository = new MemberRepository({
|
|
|
|
stripeAPIService,
|
2021-09-17 12:25:57 +03:00
|
|
|
tokenService,
|
2022-04-05 20:26:18 +03:00
|
|
|
newslettersService,
|
|
|
|
labsService,
|
2021-07-14 15:32:09 +03:00
|
|
|
productRepository,
|
|
|
|
Member,
|
2022-03-12 00:44:28 +03:00
|
|
|
MemberCancelEvent,
|
2021-07-14 15:32:09 +03:00
|
|
|
MemberSubscribeEvent,
|
|
|
|
MemberPaidSubscriptionEvent,
|
|
|
|
MemberEmailChangeEvent,
|
|
|
|
MemberStatusEvent,
|
2021-08-23 13:00:19 +03:00
|
|
|
MemberProductEvent,
|
2021-10-18 16:27:17 +03:00
|
|
|
OfferRedemption,
|
2021-07-14 15:32:09 +03:00
|
|
|
StripeCustomer,
|
|
|
|
StripeCustomerSubscription
|
|
|
|
});
|
|
|
|
|
|
|
|
const eventRepository = new EventRepository({
|
2022-01-18 17:53:51 +03:00
|
|
|
EmailRecipient,
|
2021-07-14 15:32:09 +03:00
|
|
|
MemberSubscribeEvent,
|
|
|
|
MemberPaidSubscriptionEvent,
|
|
|
|
MemberPaymentEvent,
|
|
|
|
MemberStatusEvent,
|
2022-01-18 17:53:51 +03:00
|
|
|
MemberLoginEvent,
|
|
|
|
labsService
|
2021-07-14 15:32:09 +03:00
|
|
|
});
|
|
|
|
|
2021-08-25 14:30:49 +03:00
|
|
|
const memberBREADService = new MemberBREADService({
|
2021-10-20 15:32:41 +03:00
|
|
|
offersAPI,
|
2021-09-14 14:18:34 +03:00
|
|
|
memberRepository,
|
|
|
|
emailService: {
|
2021-11-02 13:37:07 +03:00
|
|
|
async sendEmailWithMagicLink({email, requestedType}) {
|
|
|
|
return sendEmailWithMagicLink({
|
|
|
|
email,
|
|
|
|
requestedType,
|
|
|
|
options: {
|
|
|
|
forceEmailType: true
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2021-09-14 14:18:34 +03:00
|
|
|
},
|
|
|
|
labsService,
|
|
|
|
stripeService: stripeAPIService
|
2021-08-25 14:30:49 +03:00
|
|
|
});
|
|
|
|
|
2021-07-14 15:32:09 +03:00
|
|
|
const geolocationService = new GeolocationSerice();
|
|
|
|
|
|
|
|
const magicLinkService = new MagicLink({
|
|
|
|
transporter,
|
|
|
|
tokenProvider,
|
|
|
|
getSigninURL,
|
|
|
|
getText,
|
|
|
|
getHTML,
|
|
|
|
getSubject
|
|
|
|
});
|
|
|
|
|
|
|
|
const memberController = new MemberController({
|
|
|
|
memberRepository,
|
2022-01-21 15:08:31 +03:00
|
|
|
productRepository,
|
2021-07-14 15:32:09 +03:00
|
|
|
StripePrice,
|
2021-09-22 14:32:02 +03:00
|
|
|
tokenService,
|
2022-01-10 12:42:05 +03:00
|
|
|
sendEmailWithMagicLink
|
2021-07-14 15:32:09 +03:00
|
|
|
});
|
|
|
|
|
2021-10-21 14:35:29 +03:00
|
|
|
const paymentsService = new PaymentsService({
|
|
|
|
Offer,
|
|
|
|
offersAPI,
|
|
|
|
stripeAPIService
|
|
|
|
});
|
|
|
|
|
2021-07-14 15:32:09 +03:00
|
|
|
const routerController = new RouterController({
|
2021-10-13 12:11:12 +03:00
|
|
|
offersAPI,
|
2021-10-21 14:35:29 +03:00
|
|
|
paymentsService,
|
2021-10-06 16:01:04 +03:00
|
|
|
productRepository,
|
2021-07-14 15:32:09 +03:00
|
|
|
memberRepository,
|
|
|
|
StripePrice,
|
|
|
|
allowSelfSignup,
|
|
|
|
magicLinkService,
|
|
|
|
stripeAPIService,
|
|
|
|
tokenService,
|
|
|
|
sendEmailWithMagicLink,
|
2022-02-09 16:00:39 +03:00
|
|
|
labsService
|
2021-07-14 15:32:09 +03:00
|
|
|
});
|
|
|
|
|
2021-07-19 15:02:45 +03:00
|
|
|
const wellKnownController = new WellKnownController({
|
2021-12-02 17:46:58 +03:00
|
|
|
tokenService
|
2021-07-19 15:02:45 +03:00
|
|
|
});
|
|
|
|
|
2021-07-14 15:32:09 +03:00
|
|
|
async function hasActiveStripeSubscriptions() {
|
|
|
|
const firstActiveSubscription = await StripeCustomerSubscription.findOne({
|
|
|
|
status: 'active'
|
|
|
|
});
|
|
|
|
|
|
|
|
if (firstActiveSubscription) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
const firstTrialingSubscription = await StripeCustomerSubscription.findOne({
|
|
|
|
status: 'trialing'
|
|
|
|
});
|
|
|
|
|
|
|
|
if (firstTrialingSubscription) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
const firstUnpaidSubscription = await StripeCustomerSubscription.findOne({
|
|
|
|
status: 'unpaid'
|
|
|
|
});
|
|
|
|
|
|
|
|
if (firstUnpaidSubscription) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
const firstPastDueSubscription = await StripeCustomerSubscription.findOne({
|
|
|
|
status: 'past_due'
|
|
|
|
});
|
|
|
|
|
|
|
|
if (firstPastDueSubscription) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const users = memberRepository;
|
|
|
|
|
|
|
|
async function sendEmailWithMagicLink({email, requestedType, tokenData, options = {forceEmailType: false}, requestSrc = ''}) {
|
|
|
|
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, requestSrc, tokenData: Object.assign({email}, tokenData)});
|
|
|
|
}
|
|
|
|
|
|
|
|
function getMagicLink(email) {
|
|
|
|
return magicLinkService.getMagicLink({tokenData: {email}, type: 'signin'});
|
|
|
|
}
|
|
|
|
|
|
|
|
async function getMemberDataFromMagicLinkToken(token) {
|
2022-04-13 12:25:56 +03:00
|
|
|
const {email, labels = [], name = '', oldEmail, newsletters} = await magicLinkService.getDataFromToken(token);
|
2021-07-14 15:32:09 +03:00
|
|
|
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
|
|
|
|
if (oldEmail) {
|
|
|
|
member.email = email;
|
|
|
|
}
|
|
|
|
await users.update(member, {id: member.id});
|
|
|
|
return getMemberIdentityData(email);
|
|
|
|
}
|
|
|
|
return member;
|
|
|
|
}
|
2022-04-13 12:25:56 +03:00
|
|
|
const newMember = await users.create({name, email, labels, newsletters});
|
2021-07-14 15:32:09 +03:00
|
|
|
await MemberLoginEvent.add({member_id: newMember.id});
|
|
|
|
return getMemberIdentityData(email);
|
|
|
|
}
|
|
|
|
|
|
|
|
async function getMemberIdentityData(email) {
|
2021-08-26 17:07:06 +03:00
|
|
|
return memberBREADService.read({email});
|
2021-07-14 15:32:09 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2021-12-02 17:46:58 +03:00
|
|
|
throw new errors.IncorrectUsageError({
|
2021-07-14 15:32:09 +03:00
|
|
|
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) {
|
2021-12-02 17:46:58 +03:00
|
|
|
throw new errors.NotFoundError({
|
2021-07-14 15:32:09 +03:00
|
|
|
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) {
|
|
|
|
member.geolocation = geolocation;
|
|
|
|
await users.update(member, {id: member.id});
|
|
|
|
}
|
|
|
|
|
|
|
|
return getMemberIdentityData(email);
|
|
|
|
}
|
|
|
|
|
|
|
|
const middleware = {
|
|
|
|
sendMagicLink: Router().use(
|
|
|
|
body.json(),
|
|
|
|
(req, res) => routerController.sendMagicLink(req, res)
|
|
|
|
),
|
|
|
|
createCheckoutSession: Router().use(
|
|
|
|
body.json(),
|
|
|
|
(req, res) => routerController.createCheckoutSession(req, res)
|
|
|
|
),
|
|
|
|
createCheckoutSetupSession: Router().use(
|
|
|
|
body.json(),
|
|
|
|
(req, res) => routerController.createCheckoutSetupSession(req, res)
|
|
|
|
),
|
2021-09-22 13:37:37 +03:00
|
|
|
createEvents: Router().use(
|
|
|
|
body.json(),
|
2021-09-22 14:21:03 +03:00
|
|
|
(req, res) => MembersAnalyticsIngress.createEvents(req, res)
|
2021-09-22 13:37:37 +03:00
|
|
|
),
|
2021-09-22 14:32:02 +03:00
|
|
|
updateEmailAddress: Router().use(
|
|
|
|
body.json(),
|
|
|
|
(req, res) => memberController.updateEmailAddress(req, res)
|
|
|
|
),
|
2021-07-14 15:32:09 +03:00
|
|
|
updateSubscription: Router({mergeParams: true}).use(
|
|
|
|
body.json(),
|
|
|
|
(req, res) => memberController.updateSubscription(req, res)
|
|
|
|
),
|
2021-07-19 15:02:45 +03:00
|
|
|
wellKnown: Router()
|
|
|
|
.get('/jwks.json',
|
|
|
|
(req, res) => wellKnownController.getPublicKeys(req, res)
|
|
|
|
)
|
2021-07-14 15:32:09 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
const getPublicConfig = function () {
|
|
|
|
return Promise.resolve({
|
|
|
|
publicKey,
|
|
|
|
issuer
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const bus = new (require('events').EventEmitter)();
|
|
|
|
|
2022-01-17 13:10:57 +03:00
|
|
|
bus.emit('ready');
|
2021-07-14 15:32:09 +03:00
|
|
|
|
|
|
|
return {
|
|
|
|
middleware,
|
|
|
|
getMemberDataFromMagicLinkToken,
|
|
|
|
getMemberIdentityToken,
|
|
|
|
getMemberIdentityData,
|
|
|
|
setMemberGeolocationFromIp,
|
|
|
|
getPublicConfig,
|
|
|
|
bus,
|
|
|
|
sendEmailWithMagicLink,
|
|
|
|
getMagicLink,
|
|
|
|
hasActiveStripeSubscriptions,
|
|
|
|
members: users,
|
2021-08-25 14:30:49 +03:00
|
|
|
memberBREADService,
|
2021-07-14 15:32:09 +03:00
|
|
|
events: eventRepository,
|
|
|
|
productRepository
|
|
|
|
};
|
|
|
|
};
|