ghost 5.115.0 → 5.115.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/tryghost-adapter-cache-redis-5.115.1.tgz +0 -0
- package/components/tryghost-adapter-manager-5.115.1.tgz +0 -0
- package/components/{tryghost-announcement-bar-settings-5.115.0.tgz → tryghost-announcement-bar-settings-5.115.1.tgz} +0 -0
- package/components/{tryghost-api-framework-5.115.0.tgz → tryghost-api-framework-5.115.1.tgz} +0 -0
- package/components/tryghost-constants-5.115.1.tgz +0 -0
- package/components/tryghost-custom-fonts-5.115.1.tgz +0 -0
- package/components/{tryghost-custom-theme-settings-service-5.115.0.tgz → tryghost-custom-theme-settings-service-5.115.1.tgz} +0 -0
- package/components/{tryghost-data-generator-5.115.0.tgz → tryghost-data-generator-5.115.1.tgz} +0 -0
- package/components/tryghost-domain-events-5.115.1.tgz +0 -0
- package/components/tryghost-donations-5.115.1.tgz +0 -0
- package/components/{tryghost-email-addresses-5.115.0.tgz → tryghost-email-addresses-5.115.1.tgz} +0 -0
- package/components/{tryghost-email-content-generator-5.115.0.tgz → tryghost-email-content-generator-5.115.1.tgz} +0 -0
- package/components/{tryghost-email-events-5.115.0.tgz → tryghost-email-events-5.115.1.tgz} +0 -0
- package/components/tryghost-email-service-5.115.1.tgz +0 -0
- package/components/{tryghost-email-suppression-list-5.115.0.tgz → tryghost-email-suppression-list-5.115.1.tgz} +0 -0
- package/components/{tryghost-express-dynamic-redirects-5.115.0.tgz → tryghost-express-dynamic-redirects-5.115.1.tgz} +0 -0
- package/components/tryghost-ghost-5.115.1.tgz +0 -0
- package/components/tryghost-html-to-plaintext-5.115.1.tgz +0 -0
- package/components/tryghost-i18n-5.115.1.tgz +0 -0
- package/components/{tryghost-importer-handler-content-files-5.115.0.tgz → tryghost-importer-handler-content-files-5.115.1.tgz} +0 -0
- package/components/tryghost-in-memory-repository-5.115.1.tgz +0 -0
- package/components/{tryghost-job-manager-5.115.0.tgz → tryghost-job-manager-5.115.1.tgz} +0 -0
- package/components/{tryghost-link-redirects-5.115.0.tgz → tryghost-link-redirects-5.115.1.tgz} +0 -0
- package/components/tryghost-link-replacer-5.115.1.tgz +0 -0
- package/components/{tryghost-magic-link-5.115.0.tgz → tryghost-magic-link-5.115.1.tgz} +0 -0
- package/components/{tryghost-mailgun-client-5.115.0.tgz → tryghost-mailgun-client-5.115.1.tgz} +0 -0
- package/components/tryghost-member-attribution-5.115.1.tgz +0 -0
- package/components/{tryghost-member-events-5.115.0.tgz → tryghost-member-events-5.115.1.tgz} +0 -0
- package/components/{tryghost-members-api-5.115.0.tgz → tryghost-members-api-5.115.1.tgz} +0 -0
- package/components/{tryghost-members-csv-5.115.0.tgz → tryghost-members-csv-5.115.1.tgz} +0 -0
- package/components/{tryghost-members-offers-5.115.0.tgz → tryghost-members-offers-5.115.1.tgz} +0 -0
- package/components/{tryghost-members-payments-5.115.0.tgz → tryghost-members-payments-5.115.1.tgz} +0 -0
- package/components/{tryghost-milestones-5.115.0.tgz → tryghost-milestones-5.115.1.tgz} +0 -0
- package/components/{tryghost-minifier-5.115.0.tgz → tryghost-minifier-5.115.1.tgz} +0 -0
- package/components/{tryghost-mw-error-handler-5.115.0.tgz → tryghost-mw-error-handler-5.115.1.tgz} +0 -0
- package/components/{tryghost-mw-version-match-5.115.0.tgz → tryghost-mw-version-match-5.115.1.tgz} +0 -0
- package/components/tryghost-mw-vhost-5.115.1.tgz +0 -0
- package/components/tryghost-post-events-5.115.1.tgz +0 -0
- package/components/{tryghost-post-revisions-5.115.0.tgz → tryghost-post-revisions-5.115.1.tgz} +0 -0
- package/components/{tryghost-posts-service-5.115.0.tgz → tryghost-posts-service-5.115.1.tgz} +0 -0
- package/components/{tryghost-prometheus-metrics-5.115.0.tgz → tryghost-prometheus-metrics-5.115.1.tgz} +0 -0
- package/components/tryghost-recommendations-5.115.1.tgz +0 -0
- package/components/{tryghost-security-5.115.0.tgz → tryghost-security-5.115.1.tgz} +0 -0
- package/components/{tryghost-slack-notifications-5.115.0.tgz → tryghost-slack-notifications-5.115.1.tgz} +0 -0
- package/components/{tryghost-tiers-5.115.0.tgz → tryghost-tiers-5.115.1.tgz} +0 -0
- package/components/tryghost-webmentions-5.115.1.tgz +0 -0
- package/content/themes/casper/LICENSE +1 -1
- package/content/themes/casper/README.md +1 -1
- package/content/themes/source/LICENSE +1 -1
- package/content/themes/source/README.md +1 -1
- package/content/themes/source/assets/built/screen.css +1 -1
- package/content/themes/source/assets/built/screen.css.map +1 -1
- package/content/themes/source/assets/css/screen.css +11 -6
- package/content/themes/source/partials/feature-image.hbs +2 -2
- package/core/boot.js +3 -1
- package/core/built/admin/assets/admin-x-activitypub/admin-x-activitypub.js +23497 -23041
- package/core/built/admin/assets/admin-x-demo/admin-x-demo.js +1 -1
- package/core/built/admin/assets/admin-x-demo/{index-0040480a.mjs → index-15df2af5.mjs} +4 -3
- package/core/built/admin/assets/admin-x-demo/{modals-fb35c86c.mjs → modals-8ca61d78.mjs} +67 -65
- package/core/built/admin/assets/admin-x-settings/{CodeEditorView-806ef39c.mjs → CodeEditorView-d2e6872f.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +1 -1
- package/core/built/admin/assets/admin-x-settings/{index-376f847c.mjs → index-8e8821e5.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/{index-8fa19303.mjs → index-f5cb3db3.mjs} +3104 -3094
- package/core/built/admin/assets/admin-x-settings/{modals-36775d71.mjs → modals-e8ae4d46.mjs} +3 -3
- package/core/built/admin/assets/{chunk.524.31419fdf6fb3859ecc1e.js → chunk.524.2439684964c164c598ab.js} +6 -6
- package/core/built/admin/assets/{chunk.582.08c816d5e4ab766486a7.js → chunk.582.bf5a2bbb2c4eb69ef1e7.js} +10 -10
- package/core/built/admin/assets/ghost-327b17ea23cb8c89bd7e6a51e18e8506.css +1 -0
- package/core/built/admin/assets/ghost-dark-f30a597ac19632a118939492591c531b.css +1 -0
- package/core/built/admin/assets/{ghost-938b3d9c29e3564a53a22f8c8f82d351.js → ghost-df7b9558260aa27d18b195ee895b487d.js} +181 -159
- package/core/built/admin/assets/stats/stats.js +11824 -0
- package/core/built/admin/index.html +4 -4
- package/core/frontend/helpers/ghost_head.js +3 -1
- package/core/frontend/src/cards/css/cta.css +1 -1
- package/core/server/api/endpoints/slugs.js +6 -2
- package/core/server/data/importer/import-manager.js +2 -2
- package/core/server/data/importer/importers/importer-revue.js +128 -0
- package/core/server/data/importer/importers/json-to-html.js +107 -0
- package/core/server/data/migrations/utils/tables.js +2 -4
- package/core/server/lib/bootstrap-socket.js +87 -0
- package/core/server/lib/package-json/index.js +1 -0
- package/core/server/lib/package-json/package-json.js +160 -0
- package/core/server/lib/package-json/parse.js +57 -0
- package/core/server/models/base/plugins/actions.js +44 -31
- package/core/server/models/base/plugins/generate-slug.js +6 -0
- package/core/server/notify.js +1 -1
- package/core/server/services/activitypub/ActivityPubService.ts +1 -1
- package/core/server/services/api-version-compatibility/APIVersionCompatibilityService.js +99 -0
- package/core/server/services/api-version-compatibility/VersionNotificationsDataService.js +80 -0
- package/core/server/services/api-version-compatibility/extract-api-key.js +57 -0
- package/core/server/services/api-version-compatibility/index.js +2 -2
- package/core/server/services/api-version-compatibility/mw-api-version-mismatch.js +31 -0
- package/core/server/services/audience-feedback/AudienceFeedbackController.js +85 -0
- package/core/server/services/audience-feedback/AudienceFeedbackService.js +34 -0
- package/core/server/services/audience-feedback/Feedback.js +35 -0
- package/core/server/services/audience-feedback/index.js +4 -2
- package/core/server/services/auth/session/emails/signin.js +168 -0
- package/core/server/services/auth/session/index.js +2 -2
- package/core/server/services/auth/session/session-from-token.js +69 -0
- package/core/server/services/auth/session/session-service.js +364 -0
- package/core/server/services/email-analytics/EmailAnalyticsProviderMailgun.js +62 -0
- package/core/server/services/email-analytics/EmailAnalyticsService.js +552 -0
- package/core/server/services/email-analytics/EmailAnalyticsServiceWrapper.js +3 -3
- package/core/server/services/email-analytics/EventProcessingResult.js +66 -0
- package/core/server/services/explore-ping/ExplorePingService.js +106 -0
- package/core/server/services/explore-ping/index.js +31 -0
- package/core/server/services/identity-tokens/IdentityTokenService.js +30 -0
- package/core/server/services/identity-tokens/IdentityTokenService.ts +28 -0
- package/core/server/services/identity-tokens/IdentityTokenServiceWrapper.js +1 -1
- package/core/server/services/invitations/accept.js +5 -2
- package/core/server/services/mail-events/BookshelfMailEventRepository.js +2 -2
- package/core/server/services/mail-events/InMemoryMailEventRepository.js +10 -0
- package/core/server/services/mail-events/InMemoryMailEventRepository.ts +8 -0
- package/core/server/services/mail-events/MailEvent.js +20 -0
- package/core/server/services/mail-events/MailEvent.ts +10 -0
- package/core/server/services/mail-events/MailEventRepository.js +2 -0
- package/core/server/services/mail-events/MailEventRepository.ts +5 -0
- package/core/server/services/mail-events/MailEventService.js +124 -0
- package/core/server/services/mail-events/MailEventService.ts +169 -0
- package/core/server/services/mail-events/index.js +1 -1
- package/core/server/services/mail-events/libraries.d.ts +2 -0
- package/core/server/services/members/CaptchaService.js +80 -0
- package/core/server/services/members/api.js +1 -1
- package/core/server/services/members/importer/MembersCSVImporter.js +464 -0
- package/core/server/services/members/importer/MembersCSVImporterStripeUtils.js +194 -0
- package/core/server/services/members/importer/email-template.js +182 -0
- package/core/server/services/members/importer/index.js +30 -0
- package/core/server/services/members/members-ssr.js +333 -0
- package/core/server/services/members/service.js +2 -2
- package/core/server/services/posts/stats/PostStats.js +13 -0
- package/core/server/services/route-settings/SettingsPathManager.js +47 -0
- package/core/server/services/route-settings/index.js +1 -1
- package/core/server/services/stripe/README.md +63 -0
- package/core/server/services/stripe/StripeAPI.js +931 -0
- package/core/server/services/stripe/StripeMigrations.js +613 -0
- package/core/server/services/stripe/StripeService.js +175 -0
- package/core/server/services/stripe/WebhookController.js +100 -0
- package/core/server/services/stripe/WebhookManager.js +175 -0
- package/core/server/services/stripe/events/StripeLiveDisabledEvent.js +23 -0
- package/core/server/services/stripe/events/StripeLiveEnabledEvent.js +23 -0
- package/core/server/services/stripe/events/index.js +4 -0
- package/core/server/services/stripe/service.js +1 -1
- package/core/server/services/stripe/services/webhook/CheckoutSessionEventService.js +255 -0
- package/core/server/services/stripe/services/webhook/InvoiceEventService.js +70 -0
- package/core/server/services/stripe/services/webhook/SubscriptionEventService.js +54 -0
- package/core/server/services/themes/loader.js +1 -1
- package/core/server/services/themes/to-json.js +1 -1
- package/core/server/web/api/endpoints/admin/routes.js +1 -0
- package/core/server/web/shared/middleware/cache-control.js +51 -0
- package/core/server/web/shared/middleware/index.js +1 -1
- package/core/server/web/well-known.js +1 -1
- package/core/shared/labs.js +3 -1
- package/core/shared/settings-cache/CacheManager.js +64 -6
- package/package.json +103 -134
- package/tsconfig.tsbuildinfo +1 -1
- package/yarn.lock +7 -93
- package/components/tryghost-adapter-cache-redis-5.115.0.tgz +0 -0
- package/components/tryghost-adapter-manager-5.115.0.tgz +0 -0
- package/components/tryghost-api-version-compatibility-service-5.115.0.tgz +0 -0
- package/components/tryghost-audience-feedback-5.115.0.tgz +0 -0
- package/components/tryghost-bookshelf-repository-5.115.0.tgz +0 -0
- package/components/tryghost-bootstrap-socket-5.115.0.tgz +0 -0
- package/components/tryghost-captcha-service-5.115.0.tgz +0 -0
- package/components/tryghost-constants-5.115.0.tgz +0 -0
- package/components/tryghost-custom-fonts-5.115.0.tgz +0 -0
- package/components/tryghost-domain-events-5.115.0.tgz +0 -0
- package/components/tryghost-donations-5.115.0.tgz +0 -0
- package/components/tryghost-email-analytics-provider-mailgun-5.115.0.tgz +0 -0
- package/components/tryghost-email-analytics-service-5.115.0.tgz +0 -0
- package/components/tryghost-email-service-5.115.0.tgz +0 -0
- package/components/tryghost-extract-api-key-5.115.0.tgz +0 -0
- package/components/tryghost-ghost-5.115.0.tgz +0 -0
- package/components/tryghost-html-to-plaintext-5.115.0.tgz +0 -0
- package/components/tryghost-i18n-5.115.0.tgz +0 -0
- package/components/tryghost-identity-token-service-5.115.0.tgz +0 -0
- package/components/tryghost-importer-revue-5.115.0.tgz +0 -0
- package/components/tryghost-in-memory-repository-5.115.0.tgz +0 -0
- package/components/tryghost-link-replacer-5.115.0.tgz +0 -0
- package/components/tryghost-mail-events-5.115.0.tgz +0 -0
- package/components/tryghost-member-attribution-5.115.0.tgz +0 -0
- package/components/tryghost-members-importer-5.115.0.tgz +0 -0
- package/components/tryghost-members-ssr-5.115.0.tgz +0 -0
- package/components/tryghost-members-stripe-service-5.115.0.tgz +0 -0
- package/components/tryghost-mw-api-version-mismatch-5.115.0.tgz +0 -0
- package/components/tryghost-mw-cache-control-5.115.0.tgz +0 -0
- package/components/tryghost-mw-session-from-token-5.115.0.tgz +0 -0
- package/components/tryghost-mw-update-user-last-seen-5.115.0.tgz +0 -0
- package/components/tryghost-mw-vhost-5.115.0.tgz +0 -0
- package/components/tryghost-package-json-5.115.0.tgz +0 -0
- package/components/tryghost-post-events-5.115.0.tgz +0 -0
- package/components/tryghost-recommendations-5.115.0.tgz +0 -0
- package/components/tryghost-referrers-5.115.0.tgz +0 -0
- package/components/tryghost-session-service-5.115.0.tgz +0 -0
- package/components/tryghost-settings-path-manager-5.115.0.tgz +0 -0
- package/components/tryghost-version-notifications-data-service-5.115.0.tgz +0 -0
- package/components/tryghost-webmentions-5.115.0.tgz +0 -0
- package/core/built/admin/assets/ghost-c2a7c4a1b76550c4219adb2ed4124ce0.css +0 -1
- package/core/built/admin/assets/ghost-dark-f91e4a479c6d38d94d5d1b14727871dc.css +0 -1
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
const {parse: parseUrl} = require('url');
|
|
2
|
+
const createCookies = require('cookies');
|
|
3
|
+
const debug = require('@tryghost/debug')('members-ssr');
|
|
4
|
+
|
|
5
|
+
const {
|
|
6
|
+
BadRequestError,
|
|
7
|
+
IncorrectUsageError
|
|
8
|
+
} = require('@tryghost/errors');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {import('http').IncomingMessage} Request
|
|
12
|
+
* @typedef {import('http').ServerResponse} Response
|
|
13
|
+
* @typedef {import('cookies').ICookies} Cookies
|
|
14
|
+
* @typedef {import('cookies').Option} CookiesOptions
|
|
15
|
+
* @typedef {import('cookies').SetOption} SetCookieOptions
|
|
16
|
+
* @typedef {string} JWT
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {object} Member
|
|
21
|
+
* @prop {string} id
|
|
22
|
+
* @prop {string} transient_id
|
|
23
|
+
* @prop {string} email
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const SIX_MONTHS_MS = 1000 * 60 * 60 * 24 * 184;
|
|
27
|
+
|
|
28
|
+
class MembersSSR {
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {object} MembersSSROptions
|
|
31
|
+
*
|
|
32
|
+
* @prop {string|string[]} cookieKeys - A secret or array of secrets used to sign cookies
|
|
33
|
+
* @prop {() => object} getMembersApi - A function which returns an instance of members-api
|
|
34
|
+
* @prop {boolean} [cookieSecure = true] - Whether the cookie should have Secure flag
|
|
35
|
+
* @prop {string} [cookieName] - The name of the members-ssr cookie
|
|
36
|
+
* @prop {number} [cookieMaxAge] - The max age in ms of the members-ssr cookie
|
|
37
|
+
* @prop {string} [cookiePath] - The Path flag for the cookie
|
|
38
|
+
* @prop {boolean} [dangerousRemovalOfSignedCookie] - Flag for removing signed cookie
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Create an instance of MembersSSR
|
|
43
|
+
*
|
|
44
|
+
* @param {MembersSSROptions} options - The options for the members ssr class
|
|
45
|
+
*/
|
|
46
|
+
constructor(options) {
|
|
47
|
+
const {
|
|
48
|
+
cookieSecure = true,
|
|
49
|
+
cookieName = 'members-ssr',
|
|
50
|
+
cookieMaxAge = SIX_MONTHS_MS,
|
|
51
|
+
cookiePath = '/',
|
|
52
|
+
cookieKeys,
|
|
53
|
+
getMembersApi,
|
|
54
|
+
dangerousRemovalOfSignedCookie
|
|
55
|
+
} = options;
|
|
56
|
+
|
|
57
|
+
if (!getMembersApi) {
|
|
58
|
+
throw new IncorrectUsageError({message: 'Missing option getMembersApi'});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
this._getMembersApi = getMembersApi;
|
|
62
|
+
|
|
63
|
+
if (!cookieKeys) {
|
|
64
|
+
throw new IncorrectUsageError({message: 'Missing option cookieKeys'});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
this.sessionCookieName = cookieName;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @type SetCookieOptions
|
|
71
|
+
*/
|
|
72
|
+
this.sessionCookieOptions = {
|
|
73
|
+
signed: true,
|
|
74
|
+
httpOnly: true,
|
|
75
|
+
sameSite: 'lax',
|
|
76
|
+
maxAge: cookieMaxAge,
|
|
77
|
+
path: cookiePath
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
if (dangerousRemovalOfSignedCookie === true) {
|
|
81
|
+
this.sessionCookieOptions.signed = false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @type CookiesOptions
|
|
86
|
+
*/
|
|
87
|
+
this.cookiesOptions = {
|
|
88
|
+
keys: Array.isArray(cookieKeys) ? cookieKeys : [cookieKeys],
|
|
89
|
+
secure: cookieSecure
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @method _getCookies
|
|
95
|
+
*
|
|
96
|
+
* @param {Request} req
|
|
97
|
+
* @param {Response} res
|
|
98
|
+
*
|
|
99
|
+
* @returns {Cookies} An instance of the cookies object for current request/response
|
|
100
|
+
*/
|
|
101
|
+
_getCookies(req, res) {
|
|
102
|
+
return createCookies(req, res, this.cookiesOptions);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* @method _removeSessionCookie
|
|
107
|
+
*
|
|
108
|
+
* @param {Request} req
|
|
109
|
+
* @param {Response} res
|
|
110
|
+
*/
|
|
111
|
+
_removeSessionCookie(req, res) {
|
|
112
|
+
const cookies = this._getCookies(req, res);
|
|
113
|
+
cookies.set(this.sessionCookieName, null, this.sessionCookieOptions);
|
|
114
|
+
// If members caching cookies are set, remove them
|
|
115
|
+
if (cookies.get('ghost-access') || cookies.get('ghost-access-hmac')) {
|
|
116
|
+
cookies.set('ghost-access', null, {...this.sessionCookieOptions, signed: false});
|
|
117
|
+
cookies.set('ghost-access-hmac', null, {...this.sessionCookieOptions, signed: false});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* @method _setSessionCookie
|
|
123
|
+
*
|
|
124
|
+
* @param {Request} req
|
|
125
|
+
* @param {Response} res
|
|
126
|
+
* @param {string} value
|
|
127
|
+
*/
|
|
128
|
+
_setSessionCookie(req, res, value) {
|
|
129
|
+
if (!value) {
|
|
130
|
+
return this._removeSessionCookie(req, res);
|
|
131
|
+
}
|
|
132
|
+
const cookies = this._getCookies(req, res);
|
|
133
|
+
cookies.set(this.sessionCookieName, value, this.sessionCookieOptions);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @method _getSessionCookies
|
|
138
|
+
*
|
|
139
|
+
* @param {Request} req
|
|
140
|
+
* @param {Response} res
|
|
141
|
+
*
|
|
142
|
+
* @returns {string} The cookie value
|
|
143
|
+
*/
|
|
144
|
+
_getSessionCookies(req, res) {
|
|
145
|
+
const cookies = this._getCookies(req, res);
|
|
146
|
+
const value = cookies.get(this.sessionCookieName, {signed: true});
|
|
147
|
+
if (!value) {
|
|
148
|
+
throw new BadRequestError({
|
|
149
|
+
message: `Cookie ${this.sessionCookieName} not found`
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
return value;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* @method _getMemberDataFromToken
|
|
157
|
+
*
|
|
158
|
+
* @param {JWT} token
|
|
159
|
+
*
|
|
160
|
+
* @returns {Promise<Member>} member
|
|
161
|
+
*/
|
|
162
|
+
async _getMemberDataFromToken(token) {
|
|
163
|
+
const api = await this._getMembersApi();
|
|
164
|
+
return api.getMemberDataFromMagicLinkToken(token);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* @method _getMemberIdentityData
|
|
169
|
+
*
|
|
170
|
+
* @param {string} email
|
|
171
|
+
*
|
|
172
|
+
* @returns {Promise<Member>} member
|
|
173
|
+
*/
|
|
174
|
+
async _getMemberIdentityData(email) {
|
|
175
|
+
const api = await this._getMembersApi();
|
|
176
|
+
return api.getMemberIdentityData(email);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* @method _getMemberIdentityData
|
|
181
|
+
*
|
|
182
|
+
* @param {string} transientId
|
|
183
|
+
*
|
|
184
|
+
* @returns {Promise<Member>} member
|
|
185
|
+
*/
|
|
186
|
+
async _getMemberIdentityDataFromTransientId(transientId) {
|
|
187
|
+
const api = await this._getMembersApi();
|
|
188
|
+
return api.getMemberIdentityDataFromTransientId(transientId);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* @method _getMemberIdentityToken
|
|
193
|
+
*
|
|
194
|
+
* @param {string} email
|
|
195
|
+
*
|
|
196
|
+
* @returns {Promise<JWT>} member
|
|
197
|
+
*/
|
|
198
|
+
async _getMemberIdentityToken(transientId) {
|
|
199
|
+
const api = await this._getMembersApi();
|
|
200
|
+
return api.getMemberIdentityToken(transientId);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* @method _setMemberGeolocationFromIp
|
|
205
|
+
* @param {string} email
|
|
206
|
+
* @param {string} ip
|
|
207
|
+
*
|
|
208
|
+
* @returns {Promise<Member>} member
|
|
209
|
+
*/
|
|
210
|
+
async _setMemberGeolocationFromIp(email, ip) {
|
|
211
|
+
const api = await this._getMembersApi();
|
|
212
|
+
return api.setMemberGeolocationFromIp(email, ip);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* @method exchangeTokenForSession
|
|
217
|
+
* @param {Request} req
|
|
218
|
+
* @param {Response} res
|
|
219
|
+
*
|
|
220
|
+
* @returns {Promise<Member>} The member the session was created for
|
|
221
|
+
*/
|
|
222
|
+
async exchangeTokenForSession(req, res) {
|
|
223
|
+
if (!req.url) {
|
|
224
|
+
return Promise.reject(new BadRequestError({
|
|
225
|
+
message: 'Expected token param containing JWT'
|
|
226
|
+
}));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const {query} = parseUrl(req.url, true);
|
|
230
|
+
if (!query || !query.token) {
|
|
231
|
+
return Promise.reject(new BadRequestError({
|
|
232
|
+
message: 'Expected token param containing JWT'
|
|
233
|
+
}));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const token = Array.isArray(query.token) ? query.token[0] : query.token;
|
|
237
|
+
const member = await this._getMemberDataFromToken(token);
|
|
238
|
+
|
|
239
|
+
if (!member) {
|
|
240
|
+
// The member doesn't exist any longer (could be a sign in token for a member that was deleted)
|
|
241
|
+
return Promise.reject(new BadRequestError({
|
|
242
|
+
message: 'Invalid token'
|
|
243
|
+
}));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// perform and store geoip lookup for members when they log in
|
|
247
|
+
if (!member.geolocation) {
|
|
248
|
+
try {
|
|
249
|
+
await this._setMemberGeolocationFromIp(member.email, req.ip);
|
|
250
|
+
} catch (err) {
|
|
251
|
+
// no-op, we don't want to stop anything working due to
|
|
252
|
+
// geolocation lookup failing
|
|
253
|
+
debug(`Geolocation lookup failed: ${err.message}`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
this._setSessionCookie(req, res, member.transient_id);
|
|
258
|
+
|
|
259
|
+
return member;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async _cycleTransientId(memberId) {
|
|
263
|
+
const api = await this._getMembersApi();
|
|
264
|
+
return api.cycleTransientId(memberId);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* @method deleteSession
|
|
269
|
+
* @param {Request} req
|
|
270
|
+
* @param {Response} res
|
|
271
|
+
*
|
|
272
|
+
* @returns {Promise<void>}
|
|
273
|
+
*/
|
|
274
|
+
async deleteSession(req, res) {
|
|
275
|
+
if (req.body && typeof req.body === 'object' && req.body.all) {
|
|
276
|
+
// Update transient_id to invalidate all sessions
|
|
277
|
+
const member = await this.getMemberDataFromSession(req, res);
|
|
278
|
+
if (member) {
|
|
279
|
+
await this._cycleTransientId(member.id);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
this._removeSessionCookie(req, res);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* @method getMemberDataFromSession
|
|
287
|
+
*
|
|
288
|
+
* @param {Request} req
|
|
289
|
+
* @param {Response} res
|
|
290
|
+
*
|
|
291
|
+
* @returns {Promise<Member>}
|
|
292
|
+
*/
|
|
293
|
+
async getMemberDataFromSession(req, res) {
|
|
294
|
+
const transientId = this._getSessionCookies(req, res);
|
|
295
|
+
const member = await this._getMemberIdentityDataFromTransientId(transientId);
|
|
296
|
+
return member;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* @method getIdentityTokenForMemberFromSession
|
|
301
|
+
*
|
|
302
|
+
* @param {Request} req
|
|
303
|
+
* @param {Response} res
|
|
304
|
+
*
|
|
305
|
+
* @returns {Promise<JWT>} identity token
|
|
306
|
+
*/
|
|
307
|
+
async getIdentityTokenForMemberFromSession(req, res) {
|
|
308
|
+
const transientId = this._getSessionCookies(req, res);
|
|
309
|
+
const token = await this._getMemberIdentityToken(transientId);
|
|
310
|
+
if (!token) {
|
|
311
|
+
await this.deleteSession(req, res);
|
|
312
|
+
throw new BadRequestError({
|
|
313
|
+
message: 'Invalid session, could not get identity token'
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
return token;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Factory function for creating instance of MembersSSR
|
|
322
|
+
*
|
|
323
|
+
* @param {MembersSSROptions} options
|
|
324
|
+
* @returns {MembersSSR}
|
|
325
|
+
*/
|
|
326
|
+
module.exports = function create(options) {
|
|
327
|
+
if (!options) {
|
|
328
|
+
throw new IncorrectUsageError({
|
|
329
|
+
message: 'Must pass options'
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
return new MembersSSR(options);
|
|
333
|
+
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
const _ = require('lodash');
|
|
2
2
|
const errors = require('@tryghost/errors');
|
|
3
3
|
const tpl = require('@tryghost/tpl');
|
|
4
|
-
const MembersSSR = require('
|
|
4
|
+
const MembersSSR = require('./members-ssr');
|
|
5
5
|
const db = require('../../data/db');
|
|
6
6
|
const MembersConfigProvider = require('./MembersConfigProvider');
|
|
7
|
-
const makeMembersCSVImporter = require('
|
|
7
|
+
const makeMembersCSVImporter = require('./importer');
|
|
8
8
|
const MembersStats = require('./stats/MembersStats');
|
|
9
9
|
const memberJobs = require('./jobs');
|
|
10
10
|
const logging = require('@tryghost/logging');
|
|
@@ -20,6 +20,19 @@ class PostStats {
|
|
|
20
20
|
return result?.[0]?.published_at ? new Date(result?.[0]?.published_at) : null;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Returns the first published post date
|
|
25
|
+
*/
|
|
26
|
+
async getFirstPublishedPostDate() {
|
|
27
|
+
const result = await this.#db.knex.select('published_at')
|
|
28
|
+
.from('posts')
|
|
29
|
+
.whereIn('status', ['sent', 'published'])
|
|
30
|
+
.orderBy('published_at', 'asc')
|
|
31
|
+
.limit(1);
|
|
32
|
+
|
|
33
|
+
return result?.[0]?.published_at ? new Date(result?.[0]?.published_at) : null;
|
|
34
|
+
}
|
|
35
|
+
|
|
23
36
|
/**
|
|
24
37
|
* Fetches count of all published posts
|
|
25
38
|
*/
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const tpl = require('@tryghost/tpl');
|
|
3
|
+
const format = require('date-fns/format');
|
|
4
|
+
const {IncorrectUsageError} = require('@tryghost/errors');
|
|
5
|
+
|
|
6
|
+
const messages = {
|
|
7
|
+
incorrectPathsParameter: 'Attempted to setup settings path manager without paths values.'
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
class SettingsPathManager {
|
|
11
|
+
/**
|
|
12
|
+
*
|
|
13
|
+
* @param {Object} options
|
|
14
|
+
* @param {String[]} options.paths - file location paths ordered in priority by where to locate them first
|
|
15
|
+
* @param {String} options.type setting file type, e.g: 'routes' or 'redirects'
|
|
16
|
+
* @param {String[]} [options.extensions] the supported file extensions with 'yaml' and 'json' defaults. Note 'yml' extension is ignored on purpose
|
|
17
|
+
*/
|
|
18
|
+
constructor({type, paths, extensions = ['yaml', 'json']}) {
|
|
19
|
+
if (!paths || !paths.length) {
|
|
20
|
+
throw new IncorrectUsageError({
|
|
21
|
+
message: tpl(messages.incorrectPathsParameter)
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
this.type = type;
|
|
26
|
+
this.filename = type;
|
|
27
|
+
|
|
28
|
+
this.paths = paths;
|
|
29
|
+
this.defaultPath = paths[0];
|
|
30
|
+
|
|
31
|
+
this.extensions = extensions;
|
|
32
|
+
this.defaultExtension = extensions[0];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
getDefaultFilePath() {
|
|
36
|
+
const settingsFolder = this.defaultPath;
|
|
37
|
+
return path.join(settingsFolder, `${this.filename}.${this.defaultExtension}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
getBackupFilePath() {
|
|
41
|
+
const settingsFolder = this.defaultPath;
|
|
42
|
+
const dateStamp = format(new Date(), 'yyyy-MM-dd-HH-mm-ss');
|
|
43
|
+
return path.join(settingsFolder, `${this.filename}-${dateStamp}.${this.defaultExtension}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = SettingsPathManager;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const config = require('../../../shared/config');
|
|
2
2
|
const parseYaml = require('./yaml-parser');
|
|
3
|
-
const SettingsPathManager = require('@tryghost/settings-path-manager');
|
|
4
3
|
|
|
5
4
|
let settingsLoader;
|
|
6
5
|
let routeSettings;
|
|
@@ -10,6 +9,7 @@ module.exports = {
|
|
|
10
9
|
const RouteSettings = require('./RouteSettings');
|
|
11
10
|
const SettingsLoader = require('./SettingsLoader');
|
|
12
11
|
const DefaultSettingsManager = require('./DefaultSettingsManager');
|
|
12
|
+
const SettingsPathManager = require('./SettingsPathManager');
|
|
13
13
|
|
|
14
14
|
const settingsPathManager = new SettingsPathManager({type: 'routes', paths: [config.getContentPath('settings')]});
|
|
15
15
|
settingsLoader = new SettingsLoader({parseYaml, settingFilePath: settingsPathManager.getDefaultFilePath()});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Stripe Service
|
|
2
|
+
This package contains code for Ghost's Stripe integration. It interacts with Stripe's API and handles webhooks.
|
|
3
|
+
|
|
4
|
+
The main export of this package is the `StripeService` class. It includes a wrapper around the Stripe API and webhook handling logic. It is instantiated in Ghost's `core/server/services/stripe` service.
|
|
5
|
+
|
|
6
|
+
## Stripe API
|
|
7
|
+
The `StripeAPI` class is a wrapper around the Stripe API. It is used by the `StripeService` class to interact with Stripe's API.
|
|
8
|
+
|
|
9
|
+
## Stripe Webhooks
|
|
10
|
+
Ghost listens for Stripe webhooks to know when a customer has subscribed to a plan, when a subscription has been cancelled, when a payment has failed, etc.
|
|
11
|
+
|
|
12
|
+
Things to keep in mind when working with Stripe webhooks:
|
|
13
|
+
- Webhooks can arrive out of order. `checkout.session.completed` webhooks may arrive before or after `customer.subscription.created` webhooks.
|
|
14
|
+
- Webhooks can be received and processed in parallel, so you should not rely on the order of the webhooks to determine the order of operations.
|
|
15
|
+
- Operations in Stripe almost always produce multiple events, increasing the likelihood of race conditions.
|
|
16
|
+
|
|
17
|
+
See Stripe's [Webhooks Guide](https://docs.stripe.com/webhooks) for more information.
|
|
18
|
+
|
|
19
|
+
### Webhook Manager
|
|
20
|
+
This class is responsible for registering the webhook endpoints with Stripe, so Stripe knows where to send the webhooks.
|
|
21
|
+
|
|
22
|
+
### Webhook Controller
|
|
23
|
+
This class is responsible for handling the webhook events. It accepts the webhook event payload and delegates it to the appropriate handler based on the event type.
|
|
24
|
+
|
|
25
|
+
### Events
|
|
26
|
+
The Webhook Controller listens for the following events:
|
|
27
|
+
- `customer.subscription.deleted`
|
|
28
|
+
- `customer.subscription.updated`
|
|
29
|
+
- `customer.subscription.created`
|
|
30
|
+
- `invoice.payment_succeeded`
|
|
31
|
+
- `checkout.session.completed`
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
## Stripe Flows
|
|
35
|
+
|
|
36
|
+
### Checkout Session Flow: New Subscription
|
|
37
|
+
```mermaid
|
|
38
|
+
sequenceDiagram
|
|
39
|
+
actor Member as Member
|
|
40
|
+
participant Portal
|
|
41
|
+
participant Ghost
|
|
42
|
+
participant Stripe
|
|
43
|
+
|
|
44
|
+
Member->>Portal: Signs up for a paid plan
|
|
45
|
+
Portal->>Ghost: Create checkout session
|
|
46
|
+
Ghost->>Stripe: Create checkout session
|
|
47
|
+
Stripe-->>Ghost: Return session ID
|
|
48
|
+
Ghost-->>Portal: Return session ID
|
|
49
|
+
Portal->>Stripe: Redirect to checkout page
|
|
50
|
+
Note over Portal: Member enters payment details in Stripe's secure portal
|
|
51
|
+
Stripe-->>Portal: Redirect to success URL
|
|
52
|
+
|
|
53
|
+
par Webhook Events
|
|
54
|
+
Stripe->>Ghost: customer.subscription.created
|
|
55
|
+
Ghost->>Ghost: Upsert member and subscription
|
|
56
|
+
Stripe->>Ghost: checkout.session.completed
|
|
57
|
+
Ghost->>Ghost: Upsert member and subscription
|
|
58
|
+
Stripe->>Ghost: customer.subscription.updated
|
|
59
|
+
Ghost->>Ghost: Upsert member and subscription
|
|
60
|
+
Stripe->>Ghost: invoice.payment_succeeded
|
|
61
|
+
Ghost->>Ghost: Record payment
|
|
62
|
+
end
|
|
63
|
+
```
|