ghost 5.114.1 → 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.114.1.tgz → tryghost-adapter-manager-5.115.1.tgz} +0 -0
- package/components/{tryghost-announcement-bar-settings-5.114.1.tgz → tryghost-announcement-bar-settings-5.115.1.tgz} +0 -0
- package/components/{tryghost-api-framework-5.114.1.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.114.1.tgz → tryghost-custom-theme-settings-service-5.115.1.tgz} +0 -0
- package/components/{tryghost-data-generator-5.114.1.tgz → tryghost-data-generator-5.115.1.tgz} +0 -0
- package/components/{tryghost-domain-events-5.114.1.tgz → 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.1.tgz +0 -0
- package/components/{tryghost-email-content-generator-5.114.1.tgz → tryghost-email-content-generator-5.115.1.tgz} +0 -0
- package/components/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.1.tgz +0 -0
- package/components/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.114.1.tgz → 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.1.tgz +0 -0
- package/components/tryghost-in-memory-repository-5.115.1.tgz +0 -0
- package/components/{tryghost-job-manager-5.114.1.tgz → tryghost-job-manager-5.115.1.tgz} +0 -0
- package/components/{tryghost-link-redirects-5.114.1.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.114.1.tgz → tryghost-magic-link-5.115.1.tgz} +0 -0
- package/components/{tryghost-mailgun-client-5.114.1.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.114.1.tgz → tryghost-member-events-5.115.1.tgz} +0 -0
- package/components/{tryghost-members-api-5.114.1.tgz → tryghost-members-api-5.115.1.tgz} +0 -0
- package/components/{tryghost-members-csv-5.114.1.tgz → tryghost-members-csv-5.115.1.tgz} +0 -0
- package/components/{tryghost-members-offers-5.114.1.tgz → tryghost-members-offers-5.115.1.tgz} +0 -0
- package/components/{tryghost-members-payments-5.114.1.tgz → tryghost-members-payments-5.115.1.tgz} +0 -0
- package/components/{tryghost-milestones-5.114.1.tgz → tryghost-milestones-5.115.1.tgz} +0 -0
- package/components/tryghost-minifier-5.115.1.tgz +0 -0
- package/components/{tryghost-mw-error-handler-5.114.1.tgz → tryghost-mw-error-handler-5.115.1.tgz} +0 -0
- package/components/{tryghost-mw-version-match-5.114.1.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.114.1.tgz → tryghost-post-events-5.115.1.tgz} +0 -0
- package/components/{tryghost-post-revisions-5.114.1.tgz → tryghost-post-revisions-5.115.1.tgz} +0 -0
- package/components/{tryghost-posts-service-5.114.1.tgz → tryghost-posts-service-5.115.1.tgz} +0 -0
- package/components/{tryghost-prometheus-metrics-5.114.1.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.114.1.tgz → tryghost-security-5.115.1.tgz} +0 -0
- package/components/tryghost-slack-notifications-5.115.1.tgz +0 -0
- package/components/{tryghost-tiers-5.114.1.tgz → tryghost-tiers-5.115.1.tgz} +0 -0
- package/components/{tryghost-webmentions-5.114.1.tgz → 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.85c5b32bd46b91c147b9.js → chunk.524.2439684964c164c598ab.js} +7 -7
- package/core/built/admin/assets/{chunk.582.449a129a8005f03574bd.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-c563138cc2c0767bf6eefc9a2587eaa4.js → ghost-df7b9558260aa27d18b195ee895b487d.js} +182 -160
- 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/data/migrations/versions/5.115/2025-03-24-07-19-27-add-identity-read-permission-to-administrators.js +6 -0
- package/core/server/data/schema/fixtures/fixtures.json +2 -1
- 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.114.1.tgz +0 -0
- package/components/tryghost-api-version-compatibility-service-5.114.1.tgz +0 -0
- package/components/tryghost-audience-feedback-5.114.1.tgz +0 -0
- package/components/tryghost-bookshelf-repository-5.114.1.tgz +0 -0
- package/components/tryghost-bootstrap-socket-5.114.1.tgz +0 -0
- package/components/tryghost-captcha-service-5.114.1.tgz +0 -0
- package/components/tryghost-constants-5.114.1.tgz +0 -0
- package/components/tryghost-custom-fonts-5.114.1.tgz +0 -0
- package/components/tryghost-donations-5.114.1.tgz +0 -0
- package/components/tryghost-email-addresses-5.114.1.tgz +0 -0
- package/components/tryghost-email-analytics-provider-mailgun-5.114.1.tgz +0 -0
- package/components/tryghost-email-analytics-service-5.114.1.tgz +0 -0
- package/components/tryghost-email-events-5.114.1.tgz +0 -0
- package/components/tryghost-email-service-5.114.1.tgz +0 -0
- package/components/tryghost-email-suppression-list-5.114.1.tgz +0 -0
- package/components/tryghost-express-dynamic-redirects-5.114.1.tgz +0 -0
- package/components/tryghost-extract-api-key-5.114.1.tgz +0 -0
- package/components/tryghost-ghost-5.114.1.tgz +0 -0
- package/components/tryghost-i18n-5.114.1.tgz +0 -0
- package/components/tryghost-identity-token-service-5.114.1.tgz +0 -0
- package/components/tryghost-importer-handler-content-files-5.114.1.tgz +0 -0
- package/components/tryghost-importer-revue-5.114.1.tgz +0 -0
- package/components/tryghost-in-memory-repository-5.114.1.tgz +0 -0
- package/components/tryghost-link-replacer-5.114.1.tgz +0 -0
- package/components/tryghost-mail-events-5.114.1.tgz +0 -0
- package/components/tryghost-member-attribution-5.114.1.tgz +0 -0
- package/components/tryghost-members-importer-5.114.1.tgz +0 -0
- package/components/tryghost-members-ssr-5.114.1.tgz +0 -0
- package/components/tryghost-members-stripe-service-5.114.1.tgz +0 -0
- package/components/tryghost-minifier-5.114.1.tgz +0 -0
- package/components/tryghost-mw-api-version-mismatch-5.114.1.tgz +0 -0
- package/components/tryghost-mw-cache-control-5.114.1.tgz +0 -0
- package/components/tryghost-mw-session-from-token-5.114.1.tgz +0 -0
- package/components/tryghost-mw-update-user-last-seen-5.114.1.tgz +0 -0
- package/components/tryghost-mw-vhost-5.114.1.tgz +0 -0
- package/components/tryghost-package-json-5.114.1.tgz +0 -0
- package/components/tryghost-recommendations-5.114.1.tgz +0 -0
- package/components/tryghost-referrers-5.114.1.tgz +0 -0
- package/components/tryghost-session-service-5.114.1.tgz +0 -0
- package/components/tryghost-settings-path-manager-5.114.1.tgz +0 -0
- package/components/tryghost-slack-notifications-5.114.1.tgz +0 -0
- package/components/tryghost-version-notifications-data-service-5.114.1.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,66 @@
|
|
|
1
|
+
class EventProcessingResult {
|
|
2
|
+
/**
|
|
3
|
+
* @param {object} result
|
|
4
|
+
* @param {number} [result.delivered]
|
|
5
|
+
* @param {number} [result.opened]
|
|
6
|
+
* @param {number} [result.temporaryFailed]
|
|
7
|
+
* @param {number} [result.permanentFailed]
|
|
8
|
+
* @param {number} [result.unsubscribed]
|
|
9
|
+
* @param {number} [result.complained]
|
|
10
|
+
* @param {number} [result.unhandled]
|
|
11
|
+
* @param {number} [result.unprocessable]
|
|
12
|
+
* @param {number} [result.processingFailures]
|
|
13
|
+
* @param {string[]} [result.emailIds]
|
|
14
|
+
* @param {string[]} [result.memberIds]
|
|
15
|
+
*/
|
|
16
|
+
constructor(result = {}) {
|
|
17
|
+
// counts
|
|
18
|
+
this.delivered = 0;
|
|
19
|
+
this.opened = 0;
|
|
20
|
+
this.temporaryFailed = 0;
|
|
21
|
+
this.permanentFailed = 0;
|
|
22
|
+
this.unsubscribed = 0;
|
|
23
|
+
this.complained = 0;
|
|
24
|
+
this.unhandled = 0;
|
|
25
|
+
this.unprocessable = 0;
|
|
26
|
+
|
|
27
|
+
// processing failures are counted separately in addition to event type counts
|
|
28
|
+
this.processingFailures = 0;
|
|
29
|
+
|
|
30
|
+
// ids seen whilst processing ready for passing to the stats aggregator
|
|
31
|
+
this.emailIds = [];
|
|
32
|
+
this.memberIds = [];
|
|
33
|
+
|
|
34
|
+
this.merge(result);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get totalEvents() {
|
|
38
|
+
return this.delivered
|
|
39
|
+
+ this.opened
|
|
40
|
+
+ this.temporaryFailed
|
|
41
|
+
+ this.permanentFailed
|
|
42
|
+
+ this.unsubscribed
|
|
43
|
+
+ this.complained
|
|
44
|
+
+ this.unhandled
|
|
45
|
+
+ this.unprocessable;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
merge(other = {}) {
|
|
49
|
+
this.delivered += other.delivered || 0;
|
|
50
|
+
this.opened += other.opened || 0;
|
|
51
|
+
this.temporaryFailed += other.temporaryFailed || 0;
|
|
52
|
+
this.permanentFailed += other.permanentFailed || 0;
|
|
53
|
+
this.unsubscribed += other.unsubscribed || 0;
|
|
54
|
+
this.complained += other.complained || 0;
|
|
55
|
+
this.unhandled += other.unhandled || 0;
|
|
56
|
+
this.unprocessable += other.unprocessable || 0;
|
|
57
|
+
|
|
58
|
+
this.processingFailures += other.processingFailures || 0;
|
|
59
|
+
|
|
60
|
+
// TODO: come up with a cleaner way to merge these without churning through Array and Set
|
|
61
|
+
this.emailIds = Array.from(new Set([...this.emailIds, ...(other.emailIds || [])])).filter(Boolean);
|
|
62
|
+
this.memberIds = Array.from(new Set([...this.memberIds, ...(other.memberIds || [])])).filter(Boolean);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = EventProcessingResult;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
module.exports = class ExplorePingService {
|
|
2
|
+
/**
|
|
3
|
+
* @param {object} deps
|
|
4
|
+
* @param {{getPublic: () => import('../../../shared/settings-cache/CacheManager').PublicSettingsCache}} deps.settingsCache
|
|
5
|
+
* @param {object} deps.config
|
|
6
|
+
* @param {object} deps.labs
|
|
7
|
+
* @param {object} deps.logging
|
|
8
|
+
* @param {object} deps.ghostVersion
|
|
9
|
+
* @param {object} deps.request
|
|
10
|
+
* @param {{stats: {
|
|
11
|
+
* getMostRecentlyPublishedPostDate: () => Promise<Date>,
|
|
12
|
+
* getFirstPublishedPostDate: () => Promise<Date>,
|
|
13
|
+
* getTotalPostsPublished: () => Promise<number>
|
|
14
|
+
* }}} deps.posts
|
|
15
|
+
* @param {{stats: {
|
|
16
|
+
* getTotalMembers: () => Promise<number>
|
|
17
|
+
* }}} deps.members
|
|
18
|
+
*/
|
|
19
|
+
constructor({settingsCache, config, labs, logging, ghostVersion, request, posts, members}) {
|
|
20
|
+
this.settingsCache = settingsCache;
|
|
21
|
+
this.config = config;
|
|
22
|
+
this.labs = labs;
|
|
23
|
+
this.logging = logging;
|
|
24
|
+
this.ghostVersion = ghostVersion;
|
|
25
|
+
this.request = request;
|
|
26
|
+
this.posts = posts;
|
|
27
|
+
this.members = members;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async constructPayload() {
|
|
31
|
+
/* eslint-disable camelcase */
|
|
32
|
+
const {title, description, icon, locale, accent_color, twitter, facebook} = this.settingsCache.getPublic();
|
|
33
|
+
|
|
34
|
+
// Get post statistics
|
|
35
|
+
const [totalPosts, lastPublishedAt, firstPublishedAt] = await Promise.all([
|
|
36
|
+
this.posts.stats.getTotalPostsPublished(),
|
|
37
|
+
this.posts.stats.getMostRecentlyPublishedPostDate(),
|
|
38
|
+
this.posts.stats.getFirstPublishedPostDate()
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
// Get member statistics with error handling
|
|
42
|
+
let totalMembers = null;
|
|
43
|
+
try {
|
|
44
|
+
totalMembers = await this.members.stats.getTotalMembers();
|
|
45
|
+
} catch (err) {
|
|
46
|
+
this.logging.warn('Failed to fetch member statistics', {
|
|
47
|
+
error: err.message,
|
|
48
|
+
context: 'explore-ping-service'
|
|
49
|
+
});
|
|
50
|
+
// Continue without member statistics
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
ghost: this.ghostVersion.full,
|
|
55
|
+
url: this.config.get('url'),
|
|
56
|
+
title,
|
|
57
|
+
description,
|
|
58
|
+
icon,
|
|
59
|
+
locale,
|
|
60
|
+
accent_color,
|
|
61
|
+
twitter,
|
|
62
|
+
facebook,
|
|
63
|
+
posts_first: firstPublishedAt ? firstPublishedAt.toISOString() : null,
|
|
64
|
+
posts_last: lastPublishedAt ? lastPublishedAt.toISOString() : null,
|
|
65
|
+
posts_total: totalPosts,
|
|
66
|
+
members_total: totalMembers
|
|
67
|
+
};
|
|
68
|
+
/* eslint-enable camelcase */
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async makeRequest(exploreUrl, payload) {
|
|
72
|
+
const json = JSON.stringify(payload);
|
|
73
|
+
this.logging.info('Pinging Explore with Payload', exploreUrl, json);
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const response = await this.request(exploreUrl, {
|
|
77
|
+
method: 'POST',
|
|
78
|
+
body: json,
|
|
79
|
+
headers: {
|
|
80
|
+
'Content-Type': 'application/json'
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
this.logging.info('Explore Response', response.statusCode, response.statusMessage);
|
|
85
|
+
|
|
86
|
+
return response;
|
|
87
|
+
} catch (err) {
|
|
88
|
+
this.logging.warn('Explore Error', err.message);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async ping() {
|
|
93
|
+
if (!this.labs.isSet('explore')) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const exploreUrl = this.config.get('explore:url');
|
|
98
|
+
if (!exploreUrl) {
|
|
99
|
+
this.logging.warn('Explore URL not set');
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const payload = await this.constructPayload();
|
|
104
|
+
await this.makeRequest(exploreUrl, payload);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const ExplorePingService = require('./ExplorePingService');
|
|
2
|
+
const config = require('../../../shared/config');
|
|
3
|
+
const labs = require('../../../shared/labs');
|
|
4
|
+
const logging = require('@tryghost/logging');
|
|
5
|
+
const ghostVersion = require('@tryghost/version');
|
|
6
|
+
const request = require('@tryghost/request');
|
|
7
|
+
const settingsCache = require('../../../shared/settings-cache');
|
|
8
|
+
const posts = require('../posts/posts-service');
|
|
9
|
+
const members = require('../members');
|
|
10
|
+
|
|
11
|
+
// Export the creation function for testing
|
|
12
|
+
module.exports.createService = function createService() {
|
|
13
|
+
return new ExplorePingService({
|
|
14
|
+
settingsCache,
|
|
15
|
+
config,
|
|
16
|
+
labs,
|
|
17
|
+
logging,
|
|
18
|
+
ghostVersion,
|
|
19
|
+
request,
|
|
20
|
+
posts: posts(),
|
|
21
|
+
members
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
module.exports.init = async function init() {
|
|
26
|
+
const explorePingService = module.exports.createService();
|
|
27
|
+
|
|
28
|
+
// The final intention is to have this run on a schedule
|
|
29
|
+
// For the initial version, we'll just ping when the server starts
|
|
30
|
+
await explorePingService.ping();
|
|
31
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.IdentityTokenService = void 0;
|
|
4
|
+
const jsonwebtoken_1 = require("jsonwebtoken");
|
|
5
|
+
class IdentityTokenService {
|
|
6
|
+
privateKey;
|
|
7
|
+
issuer;
|
|
8
|
+
keyId;
|
|
9
|
+
constructor(privateKey, issuer, keyId) {
|
|
10
|
+
this.privateKey = privateKey;
|
|
11
|
+
this.issuer = issuer;
|
|
12
|
+
this.keyId = keyId;
|
|
13
|
+
}
|
|
14
|
+
async getTokenForUser(email, role) {
|
|
15
|
+
const claims = {
|
|
16
|
+
sub: email
|
|
17
|
+
};
|
|
18
|
+
if (typeof role === 'string') {
|
|
19
|
+
claims.role = role;
|
|
20
|
+
}
|
|
21
|
+
const token = (0, jsonwebtoken_1.sign)(claims, this.privateKey, {
|
|
22
|
+
issuer: this.issuer,
|
|
23
|
+
expiresIn: '5m',
|
|
24
|
+
algorithm: 'RS256',
|
|
25
|
+
keyid: this.keyId
|
|
26
|
+
});
|
|
27
|
+
return token;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
exports.IdentityTokenService = IdentityTokenService;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {sign} from 'jsonwebtoken';
|
|
2
|
+
|
|
3
|
+
export class IdentityTokenService {
|
|
4
|
+
constructor(
|
|
5
|
+
private privateKey: string,
|
|
6
|
+
private issuer: string,
|
|
7
|
+
private keyId: string
|
|
8
|
+
) {}
|
|
9
|
+
|
|
10
|
+
async getTokenForUser(email: string, role?: string) {
|
|
11
|
+
const claims: Record<string, string> = {
|
|
12
|
+
sub: email
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
if (typeof role === 'string') {
|
|
16
|
+
claims.role = role;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const token = sign(claims, this.privateKey, {
|
|
20
|
+
issuer: this.issuer,
|
|
21
|
+
expiresIn: '5m',
|
|
22
|
+
algorithm: 'RS256',
|
|
23
|
+
keyid: this.keyId
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
return token;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -2,13 +2,16 @@ const errors = require('@tryghost/errors');
|
|
|
2
2
|
const tpl = require('@tryghost/tpl');
|
|
3
3
|
const models = require('../../models');
|
|
4
4
|
const security = require('@tryghost/security');
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
const messages = {
|
|
7
|
+
inviteNotFound: 'Invite not found.',
|
|
6
8
|
inviteExpired: 'Invite is expired.',
|
|
7
9
|
inviteEmailAlreadyExist: {
|
|
8
10
|
message: 'Could not create an account, email is already in use.',
|
|
9
11
|
context: 'Attempting to create an account with existing email address.',
|
|
10
12
|
help: 'Use different email address to register your account.'
|
|
11
|
-
}
|
|
13
|
+
}
|
|
14
|
+
};
|
|
12
15
|
|
|
13
16
|
async function accept(invitation) {
|
|
14
17
|
const data = invitation.invitation[0];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @typedef {import('
|
|
3
|
-
* @typedef {import('
|
|
2
|
+
* @typedef {import('./MailEventRepository')} MailEventRepository
|
|
3
|
+
* @typedef {import('./MailEvent').MailEvent} MailEvent
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InMemoryMailEventRepository = void 0;
|
|
4
|
+
const in_memory_repository_1 = require("@tryghost/in-memory-repository");
|
|
5
|
+
class InMemoryMailEventRepository extends in_memory_repository_1.InMemoryRepository {
|
|
6
|
+
toPrimitive() {
|
|
7
|
+
return {};
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
exports.InMemoryMailEventRepository = InMemoryMailEventRepository;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MailEvent = void 0;
|
|
4
|
+
class MailEvent {
|
|
5
|
+
id;
|
|
6
|
+
type;
|
|
7
|
+
messageId;
|
|
8
|
+
recipient;
|
|
9
|
+
timestampMs;
|
|
10
|
+
deleted;
|
|
11
|
+
constructor(id, type, messageId, recipient, timestampMs, deleted = false) {
|
|
12
|
+
this.id = id;
|
|
13
|
+
this.type = type;
|
|
14
|
+
this.messageId = messageId;
|
|
15
|
+
this.recipient = recipient;
|
|
16
|
+
this.timestampMs = timestampMs;
|
|
17
|
+
this.deleted = deleted;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
exports.MailEvent = MailEvent;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.MailEventService = void 0;
|
|
7
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
8
|
+
const errors_1 = __importDefault(require("@tryghost/errors"));
|
|
9
|
+
const tpl_1 = __importDefault(require("@tryghost/tpl"));
|
|
10
|
+
const MailEvent_1 = require("./MailEvent");
|
|
11
|
+
/**
|
|
12
|
+
* @see https://documentation.mailgun.com/en/latest/user_manual.html#events-1
|
|
13
|
+
*/
|
|
14
|
+
var EventType;
|
|
15
|
+
(function (EventType) {
|
|
16
|
+
EventType["CLICKED"] = "clicked";
|
|
17
|
+
EventType["COMPLAINED"] = "complained";
|
|
18
|
+
EventType["DELIVERED"] = "delivered";
|
|
19
|
+
EventType["FAILED"] = "failed";
|
|
20
|
+
EventType["OPENED"] = "opened";
|
|
21
|
+
EventType["UNSUBSCRIBED"] = "unsubscribed";
|
|
22
|
+
})(EventType || (EventType = {}));
|
|
23
|
+
const VALIDATION_MESSAGES = {
|
|
24
|
+
signingKeyNotConfigured: 'payload signing key is not configured',
|
|
25
|
+
payloadSignatureMissing: 'Payload is missing "signature"',
|
|
26
|
+
payloadSignatureInvalid: '"signature" is invalid',
|
|
27
|
+
payloadEventsMissing: 'Payload is missing "mail_events"',
|
|
28
|
+
payloadEventsInvalid: '"mail_events" is not an array',
|
|
29
|
+
payloadEventKeyMissing: 'Event [{idx}] is missing "{key}"'
|
|
30
|
+
};
|
|
31
|
+
class MailEventService {
|
|
32
|
+
mailEventRepository;
|
|
33
|
+
config;
|
|
34
|
+
labs;
|
|
35
|
+
static LABS_KEY = 'mailEvents';
|
|
36
|
+
static CONFIG_KEY_PAYLOAD_SIGNING_KEY = 'hostSettings:mailEventsPayloadSigningKey';
|
|
37
|
+
constructor(mailEventRepository, config, labs) {
|
|
38
|
+
this.mailEventRepository = mailEventRepository;
|
|
39
|
+
this.config = config;
|
|
40
|
+
this.labs = labs;
|
|
41
|
+
}
|
|
42
|
+
async processPayload(payload) {
|
|
43
|
+
if (this.labs.isSet(MailEventService.LABS_KEY) === false) {
|
|
44
|
+
throw new errors_1.default.NotFoundError();
|
|
45
|
+
}
|
|
46
|
+
const payloadSigningKey = this.config.get(MailEventService.CONFIG_KEY_PAYLOAD_SIGNING_KEY);
|
|
47
|
+
// Verify that the service is configured correctly - We expect a string
|
|
48
|
+
// for the payload signing key but as a safeguard we check the type here
|
|
49
|
+
// to prevent any unexpected behaviour
|
|
50
|
+
if (typeof payloadSigningKey !== 'string') {
|
|
51
|
+
throw new errors_1.default.InternalServerError({
|
|
52
|
+
message: (0, tpl_1.default)(VALIDATION_MESSAGES.signingKeyNotConfigured)
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
// Verify the payload
|
|
56
|
+
this.verifyPayload(payload, payloadSigningKey);
|
|
57
|
+
// Store known events
|
|
58
|
+
const eventTypes = new Set(Object.values(EventType));
|
|
59
|
+
for (const payloadEvent of payload.mail_events) {
|
|
60
|
+
if (eventTypes.has(payloadEvent.event) === false) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
await this.mailEventRepository.save(new MailEvent_1.MailEvent(payloadEvent.id, payloadEvent.event, payloadEvent.message.headers['message-id'], payloadEvent.recipient, payloadEvent.timestamp * 1000));
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
throw new errors_1.default.InternalServerError({
|
|
68
|
+
message: 'Event could not be stored',
|
|
69
|
+
err: err
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
validatePayload(payload) {
|
|
75
|
+
if (payload.signature === undefined) {
|
|
76
|
+
throw new errors_1.default.ValidationError({
|
|
77
|
+
message: (0, tpl_1.default)(VALIDATION_MESSAGES.payloadSignatureMissing)
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
if (typeof payload.signature !== 'string') {
|
|
81
|
+
throw new errors_1.default.ValidationError({
|
|
82
|
+
message: (0, tpl_1.default)(VALIDATION_MESSAGES.payloadSignatureInvalid)
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
if (payload.mail_events === undefined) {
|
|
86
|
+
throw new errors_1.default.ValidationError({
|
|
87
|
+
message: (0, tpl_1.default)(VALIDATION_MESSAGES.payloadEventsMissing)
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
if (Array.isArray(payload.mail_events) === false) {
|
|
91
|
+
throw new errors_1.default.ValidationError({
|
|
92
|
+
message: (0, tpl_1.default)(VALIDATION_MESSAGES.payloadEventsInvalid)
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
const expectedKeys = ['id', 'timestamp', 'event', 'message', 'recipient'];
|
|
96
|
+
payload.mail_events.forEach((payloadEvent, idx) => {
|
|
97
|
+
expectedKeys.forEach((key) => {
|
|
98
|
+
if (payloadEvent[key] === undefined) {
|
|
99
|
+
throw new errors_1.default.ValidationError({
|
|
100
|
+
message: (0, tpl_1.default)(VALIDATION_MESSAGES.payloadEventKeyMissing, { idx, key })
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
if (key === 'message' && payloadEvent.message.headers?.['message-id'] === undefined) {
|
|
104
|
+
throw new errors_1.default.ValidationError({
|
|
105
|
+
message: (0, tpl_1.default)(VALIDATION_MESSAGES.payloadEventKeyMissing, { idx, key: 'message.headers.message-id' })
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
verifyPayload(payload, payloadSigningKey) {
|
|
112
|
+
const data = JSON.stringify(payload.mail_events);
|
|
113
|
+
const signature = crypto_1.default
|
|
114
|
+
.createHmac('sha256', payloadSigningKey)
|
|
115
|
+
.update(data)
|
|
116
|
+
.digest('hex');
|
|
117
|
+
if (signature !== payload.signature) {
|
|
118
|
+
throw new errors_1.default.UnauthorizedError({
|
|
119
|
+
message: (0, tpl_1.default)(VALIDATION_MESSAGES.payloadSignatureInvalid)
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
exports.MailEventService = MailEventService;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
import errors from '@tryghost/errors';
|
|
3
|
+
import tpl from '@tryghost/tpl';
|
|
4
|
+
|
|
5
|
+
import {MailEvent} from './MailEvent';
|
|
6
|
+
import {MailEventRepository} from './MailEventRepository';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @see https://documentation.mailgun.com/en/latest/user_manual.html#events-1
|
|
10
|
+
*/
|
|
11
|
+
enum EventType { // eslint-disable-line no-shadow
|
|
12
|
+
CLICKED = 'clicked',
|
|
13
|
+
COMPLAINED = 'complained',
|
|
14
|
+
DELIVERED = 'delivered',
|
|
15
|
+
FAILED = 'failed',
|
|
16
|
+
OPENED = 'opened',
|
|
17
|
+
UNSUBSCRIBED = 'unsubscribed'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface PayloadEvent {
|
|
21
|
+
id: string;
|
|
22
|
+
timestamp: number; // Unix timestamp in seconds
|
|
23
|
+
event: string;
|
|
24
|
+
message: {
|
|
25
|
+
headers: {
|
|
26
|
+
'message-id': string;
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
recipient: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface Payload {
|
|
33
|
+
signature: string;
|
|
34
|
+
mail_events: PayloadEvent[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface Labs {
|
|
38
|
+
isSet(key: string): boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface Config {
|
|
42
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
43
|
+
get(key: string): any;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const VALIDATION_MESSAGES = {
|
|
47
|
+
signingKeyNotConfigured: 'payload signing key is not configured',
|
|
48
|
+
payloadSignatureMissing: 'Payload is missing "signature"',
|
|
49
|
+
payloadSignatureInvalid: '"signature" is invalid',
|
|
50
|
+
payloadEventsMissing: 'Payload is missing "mail_events"',
|
|
51
|
+
payloadEventsInvalid: '"mail_events" is not an array',
|
|
52
|
+
payloadEventKeyMissing: 'Event [{idx}] is missing "{key}"'
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export class MailEventService {
|
|
56
|
+
static readonly LABS_KEY = 'mailEvents';
|
|
57
|
+
static readonly CONFIG_KEY_PAYLOAD_SIGNING_KEY = 'hostSettings:mailEventsPayloadSigningKey';
|
|
58
|
+
|
|
59
|
+
constructor(
|
|
60
|
+
private mailEventRepository: MailEventRepository,
|
|
61
|
+
private config: Config,
|
|
62
|
+
private labs: Labs
|
|
63
|
+
) {}
|
|
64
|
+
|
|
65
|
+
async processPayload(payload: Payload) {
|
|
66
|
+
if (this.labs.isSet(MailEventService.LABS_KEY) === false) {
|
|
67
|
+
throw new errors.NotFoundError();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const payloadSigningKey = this.config.get(MailEventService.CONFIG_KEY_PAYLOAD_SIGNING_KEY);
|
|
71
|
+
|
|
72
|
+
// Verify that the service is configured correctly - We expect a string
|
|
73
|
+
// for the payload signing key but as a safeguard we check the type here
|
|
74
|
+
// to prevent any unexpected behaviour
|
|
75
|
+
if (typeof payloadSigningKey !== 'string') {
|
|
76
|
+
throw new errors.InternalServerError({
|
|
77
|
+
message: tpl(VALIDATION_MESSAGES.signingKeyNotConfigured)
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Verify the payload
|
|
82
|
+
this.verifyPayload(payload, payloadSigningKey);
|
|
83
|
+
|
|
84
|
+
// Store known events
|
|
85
|
+
const eventTypes = new Set<string>(Object.values(EventType) as string[]);
|
|
86
|
+
|
|
87
|
+
for (const payloadEvent of payload.mail_events) {
|
|
88
|
+
if (eventTypes.has(payloadEvent.event) === false) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
await this.mailEventRepository.save(
|
|
94
|
+
new MailEvent(
|
|
95
|
+
payloadEvent.id,
|
|
96
|
+
payloadEvent.event,
|
|
97
|
+
payloadEvent.message.headers['message-id'],
|
|
98
|
+
payloadEvent.recipient,
|
|
99
|
+
payloadEvent.timestamp * 1000
|
|
100
|
+
)
|
|
101
|
+
);
|
|
102
|
+
} catch (err) {
|
|
103
|
+
throw new errors.InternalServerError({
|
|
104
|
+
message: 'Event could not be stored',
|
|
105
|
+
err: err
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
validatePayload(payload: Payload) {
|
|
112
|
+
if (payload.signature === undefined) {
|
|
113
|
+
throw new errors.ValidationError({
|
|
114
|
+
message: tpl(VALIDATION_MESSAGES.payloadSignatureMissing)
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (typeof payload.signature !== 'string') {
|
|
119
|
+
throw new errors.ValidationError({
|
|
120
|
+
message: tpl(VALIDATION_MESSAGES.payloadSignatureInvalid)
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (payload.mail_events === undefined) {
|
|
125
|
+
throw new errors.ValidationError({
|
|
126
|
+
message: tpl(VALIDATION_MESSAGES.payloadEventsMissing)
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (Array.isArray(payload.mail_events) === false) {
|
|
131
|
+
throw new errors.ValidationError({
|
|
132
|
+
message: tpl(VALIDATION_MESSAGES.payloadEventsInvalid)
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const expectedKeys: (keyof PayloadEvent)[] = ['id', 'timestamp', 'event', 'message', 'recipient'];
|
|
137
|
+
|
|
138
|
+
payload.mail_events.forEach((payloadEvent, idx) => {
|
|
139
|
+
expectedKeys.forEach((key) => {
|
|
140
|
+
if (payloadEvent[key] === undefined) {
|
|
141
|
+
throw new errors.ValidationError({
|
|
142
|
+
message: tpl(VALIDATION_MESSAGES.payloadEventKeyMissing, {idx, key})
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (key === 'message' && payloadEvent.message.headers?.['message-id'] === undefined) {
|
|
147
|
+
throw new errors.ValidationError({
|
|
148
|
+
message: tpl(VALIDATION_MESSAGES.payloadEventKeyMissing, {idx, key: 'message.headers.message-id'})
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private verifyPayload(payload: Payload, payloadSigningKey: string) {
|
|
156
|
+
const data = JSON.stringify(payload.mail_events);
|
|
157
|
+
|
|
158
|
+
const signature = crypto
|
|
159
|
+
.createHmac('sha256', payloadSigningKey)
|
|
160
|
+
.update(data)
|
|
161
|
+
.digest('hex');
|
|
162
|
+
|
|
163
|
+
if (signature !== payload.signature) {
|
|
164
|
+
throw new errors.UnauthorizedError({
|
|
165
|
+
message: tpl(VALIDATION_MESSAGES.payloadSignatureInvalid)
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|