ghost 5.80.1 → 5.80.3
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-memory-ttl-5.80.3.tgz +0 -0
- package/components/{tryghost-adapter-cache-redis-5.80.1.tgz → tryghost-adapter-cache-redis-5.80.3.tgz} +0 -0
- package/components/{tryghost-adapter-manager-5.80.1.tgz → tryghost-adapter-manager-5.80.3.tgz} +0 -0
- package/components/tryghost-announcement-bar-settings-5.80.3.tgz +0 -0
- package/components/{tryghost-api-framework-5.80.1.tgz → tryghost-api-framework-5.80.3.tgz} +0 -0
- package/components/{tryghost-api-version-compatibility-service-5.80.1.tgz → tryghost-api-version-compatibility-service-5.80.3.tgz} +0 -0
- package/components/tryghost-audience-feedback-5.80.3.tgz +0 -0
- package/components/{tryghost-bookshelf-repository-5.80.1.tgz → tryghost-bookshelf-repository-5.80.3.tgz} +0 -0
- package/components/{tryghost-bootstrap-socket-5.80.1.tgz → tryghost-bootstrap-socket-5.80.3.tgz} +0 -0
- package/components/tryghost-collections-5.80.3.tgz +0 -0
- package/components/{tryghost-constants-5.80.1.tgz → tryghost-constants-5.80.3.tgz} +0 -0
- package/components/tryghost-custom-theme-settings-service-5.80.3.tgz +0 -0
- package/components/{tryghost-data-generator-5.80.1.tgz → tryghost-data-generator-5.80.3.tgz} +0 -0
- package/components/tryghost-domain-events-5.80.3.tgz +0 -0
- package/components/tryghost-donations-5.80.3.tgz +0 -0
- package/components/tryghost-dynamic-routing-events-5.80.3.tgz +0 -0
- package/components/tryghost-email-addresses-5.80.3.tgz +0 -0
- package/components/tryghost-email-analytics-provider-mailgun-5.80.3.tgz +0 -0
- package/components/tryghost-email-analytics-service-5.80.3.tgz +0 -0
- package/components/tryghost-email-content-generator-5.80.3.tgz +0 -0
- package/components/{tryghost-email-events-5.80.1.tgz → tryghost-email-events-5.80.3.tgz} +0 -0
- package/components/tryghost-email-service-5.80.3.tgz +0 -0
- package/components/tryghost-email-suppression-list-5.80.3.tgz +0 -0
- package/components/{tryghost-express-dynamic-redirects-5.80.1.tgz → tryghost-express-dynamic-redirects-5.80.3.tgz} +0 -0
- package/components/tryghost-external-media-inliner-5.80.3.tgz +0 -0
- package/components/tryghost-extract-api-key-5.80.3.tgz +0 -0
- package/components/tryghost-ghost-5.80.3.tgz +0 -0
- package/components/{tryghost-html-to-plaintext-5.80.1.tgz → tryghost-html-to-plaintext-5.80.3.tgz} +0 -0
- package/components/tryghost-i18n-5.80.3.tgz +0 -0
- package/components/tryghost-importer-handler-content-files-5.80.3.tgz +0 -0
- package/components/{tryghost-importer-revue-5.80.1.tgz → tryghost-importer-revue-5.80.3.tgz} +0 -0
- package/components/tryghost-in-memory-repository-5.80.3.tgz +0 -0
- package/components/tryghost-job-manager-5.80.3.tgz +0 -0
- package/components/tryghost-link-redirects-5.80.3.tgz +0 -0
- package/components/tryghost-link-replacer-5.80.3.tgz +0 -0
- package/components/tryghost-link-tracking-5.80.3.tgz +0 -0
- package/components/tryghost-magic-link-5.80.3.tgz +0 -0
- package/components/tryghost-mail-events-5.80.3.tgz +0 -0
- package/components/tryghost-mailgun-client-5.80.3.tgz +0 -0
- package/components/tryghost-member-attribution-5.80.3.tgz +0 -0
- package/components/{tryghost-member-events-5.80.1.tgz → tryghost-member-events-5.80.3.tgz} +0 -0
- package/components/tryghost-members-api-5.80.3.tgz +0 -0
- package/components/tryghost-members-csv-5.80.3.tgz +0 -0
- package/components/{tryghost-members-events-service-5.80.1.tgz → tryghost-members-events-service-5.80.3.tgz} +0 -0
- package/components/tryghost-members-importer-5.80.3.tgz +0 -0
- package/components/{tryghost-members-offers-5.80.1.tgz → tryghost-members-offers-5.80.3.tgz} +0 -0
- package/components/tryghost-members-payments-5.80.3.tgz +0 -0
- package/components/tryghost-members-ssr-5.80.3.tgz +0 -0
- package/components/{tryghost-members-stripe-service-5.80.1.tgz → tryghost-members-stripe-service-5.80.3.tgz} +0 -0
- package/components/tryghost-mentions-email-report-5.80.3.tgz +0 -0
- package/components/tryghost-milestones-5.80.3.tgz +0 -0
- package/components/tryghost-minifier-5.80.3.tgz +0 -0
- package/components/{tryghost-model-to-domain-event-interceptor-5.80.1.tgz → tryghost-model-to-domain-event-interceptor-5.80.3.tgz} +0 -0
- package/components/tryghost-mw-api-version-mismatch-5.80.3.tgz +0 -0
- package/components/tryghost-mw-cache-control-5.80.3.tgz +0 -0
- package/components/{tryghost-mw-error-handler-5.80.1.tgz → tryghost-mw-error-handler-5.80.3.tgz} +0 -0
- package/components/{tryghost-mw-session-from-token-5.80.1.tgz → tryghost-mw-session-from-token-5.80.3.tgz} +0 -0
- package/components/{tryghost-mw-update-user-last-seen-5.80.1.tgz → tryghost-mw-update-user-last-seen-5.80.3.tgz} +0 -0
- package/components/tryghost-mw-version-match-5.80.3.tgz +0 -0
- package/components/tryghost-mw-vhost-5.80.3.tgz +0 -0
- package/components/tryghost-nql-filter-expansions-5.80.3.tgz +0 -0
- package/components/tryghost-oembed-service-5.80.3.tgz +0 -0
- package/components/tryghost-package-json-5.80.3.tgz +0 -0
- package/components/{tryghost-post-events-5.80.1.tgz → tryghost-post-events-5.80.3.tgz} +0 -0
- package/components/tryghost-post-revisions-5.80.3.tgz +0 -0
- package/components/tryghost-posts-service-5.80.3.tgz +0 -0
- package/components/tryghost-recommendations-5.80.3.tgz +0 -0
- package/components/tryghost-referrers-5.80.3.tgz +0 -0
- package/components/{tryghost-security-5.80.1.tgz → tryghost-security-5.80.3.tgz} +0 -0
- package/components/tryghost-session-service-5.80.3.tgz +0 -0
- package/components/tryghost-settings-path-manager-5.80.3.tgz +0 -0
- package/components/tryghost-slack-notifications-5.80.3.tgz +0 -0
- package/components/{tryghost-staff-service-5.80.1.tgz → tryghost-staff-service-5.80.3.tgz} +0 -0
- package/components/{tryghost-stats-service-5.80.1.tgz → tryghost-stats-service-5.80.3.tgz} +0 -0
- package/components/tryghost-tiers-5.80.3.tgz +0 -0
- package/components/{tryghost-update-check-service-5.80.1.tgz → tryghost-update-check-service-5.80.3.tgz} +0 -0
- package/components/tryghost-verification-trigger-5.80.3.tgz +0 -0
- package/components/{tryghost-version-notifications-data-service-5.80.1.tgz → tryghost-version-notifications-data-service-5.80.3.tgz} +0 -0
- package/components/tryghost-webmentions-5.80.3.tgz +0 -0
- package/content/themes/casper/assets/built/screen.css +1 -1
- package/content/themes/casper/assets/built/screen.css.map +1 -1
- package/content/themes/casper/assets/css/screen.css +4 -0
- package/content/themes/casper/default.hbs +3 -3
- package/content/themes/casper/package.json +1 -1
- package/content/themes/casper/partials/post-card.hbs +1 -1
- package/content/themes/casper/post.hbs +3 -3
- 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/built/source.js +1 -1
- package/content/themes/source/assets/built/source.js.map +1 -1
- package/content/themes/source/assets/css/screen.css +80 -33
- package/content/themes/source/assets/js/{casper.js → main.js} +14 -0
- package/content/themes/source/package.json +16 -1
- package/content/themes/source/partials/components/cta.hbs +1 -1
- package/content/themes/source/partials/components/footer.hbs +2 -2
- package/content/themes/source/partials/components/header-content.hbs +2 -2
- package/content/themes/source/partials/components/navigation.hbs +1 -1
- package/content/themes/source/partials/email-subscription.hbs +2 -2
- package/content/themes/source/partials/icons/lock.hbs +1 -5
- package/content/themes/source/partials/post-card.hbs +13 -3
- package/content/themes/source/post.hbs +18 -14
- package/core/boot.js +27 -0
- package/core/built/admin/assets/admin-x-demo/admin-x-demo.js +1 -1
- package/core/built/admin/assets/admin-x-demo/{index-0722d3aa.mjs → index-771f4964.mjs} +2 -2
- package/core/built/admin/assets/admin-x-demo/{modals-969dbbd3.mjs → modals-4c390700.mjs} +46 -46
- package/core/built/admin/assets/admin-x-settings/{CodeEditorView-cd254a7a.mjs → CodeEditorView-00610c2f.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +2 -2
- package/core/built/admin/assets/admin-x-settings/{index-b52632c4.mjs → index-2deb7a1f.mjs} +4273 -4908
- package/core/built/admin/assets/admin-x-settings/{index-613b352d.mjs → index-e308aac8.mjs} +2859 -2831
- package/core/built/admin/assets/admin-x-settings/{modals-99dbacc0.mjs → modals-e2ca767c.mjs} +13267 -10619
- package/core/built/admin/assets/{chunk.524.eecfc0cb2091306f132c.js → chunk.524.c6ed93ff44a9cb49235c.js} +6 -6
- package/core/built/admin/assets/{chunk.582.e04c74f002928b557602.js → chunk.582.4e31037f1eb4c88f017f.js} +4 -4
- package/core/built/admin/assets/{chunk.763.6b160e403073ecabbfa6.js → chunk.763.7849440ec6494d2449e9.js} +997 -1197
- package/core/built/admin/assets/{ghost-ea7a51891dfca9b7a04aacfffe7dea4e.js → ghost-594a7dd26bdb4fec210b285cab5555cb.js} +14 -14
- package/core/built/admin/assets/ghost-71cfcdf9410af9b3aec11a28f9a4130f.css +1 -0
- package/core/built/admin/assets/ghost-dark-1e5bec737be037b1b5cb2a423603271d.css +1 -0
- package/core/built/admin/assets/{vendor-6b83da0c6e6fede335947219c59e1543.js → vendor-43a631b7c834235c4ff0b2186b5ca27d.js} +7 -9
- package/core/built/admin/index.html +6 -6
- package/core/frontend/helpers/get.js +89 -2
- package/core/frontend/public/robots.txt +0 -1
- package/core/frontend/services/rendering/renderer.js +19 -1
- package/core/frontend/services/theme-engine/i18n/I18n.js +7 -6
- package/core/frontend/services/theme-engine/i18n/ThemeI18n.js +4 -3
- package/core/server/adapters/storage/LocalStorageBase.js +13 -1
- package/core/server/api/endpoints/images.js +14 -3
- package/core/server/api/endpoints/utils/serializers/input/tiers.js +30 -2
- package/core/server/services/auth/api-key/admin.js +92 -90
- package/core/server/services/members/service.js +3 -1
- package/core/server/services/slack-notifications/service.js +6 -3
- package/core/server/services/themes/installer.js +1 -1
- package/core/server/services/tiers/TierRepository.js +2 -1
- package/core/server/web/admin/app.js +18 -1
- package/core/server/web/api/endpoints/admin/app.js +10 -0
- package/core/shared/labs.js +1 -0
- package/package.json +152 -150
- package/yarn.lock +532 -324
- package/components/tryghost-adapter-cache-memory-ttl-5.80.1.tgz +0 -0
- package/components/tryghost-announcement-bar-settings-5.80.1.tgz +0 -0
- package/components/tryghost-audience-feedback-5.80.1.tgz +0 -0
- package/components/tryghost-collections-5.80.1.tgz +0 -0
- package/components/tryghost-custom-theme-settings-service-5.80.1.tgz +0 -0
- package/components/tryghost-domain-events-5.80.1.tgz +0 -0
- package/components/tryghost-donations-5.80.1.tgz +0 -0
- package/components/tryghost-dynamic-routing-events-5.80.1.tgz +0 -0
- package/components/tryghost-email-addresses-5.80.1.tgz +0 -0
- package/components/tryghost-email-analytics-provider-mailgun-5.80.1.tgz +0 -0
- package/components/tryghost-email-analytics-service-5.80.1.tgz +0 -0
- package/components/tryghost-email-content-generator-5.80.1.tgz +0 -0
- package/components/tryghost-email-service-5.80.1.tgz +0 -0
- package/components/tryghost-email-suppression-list-5.80.1.tgz +0 -0
- package/components/tryghost-external-media-inliner-5.80.1.tgz +0 -0
- package/components/tryghost-extract-api-key-5.80.1.tgz +0 -0
- package/components/tryghost-i18n-5.80.1.tgz +0 -0
- package/components/tryghost-importer-handler-content-files-5.80.1.tgz +0 -0
- package/components/tryghost-in-memory-repository-5.80.1.tgz +0 -0
- package/components/tryghost-job-manager-5.80.1.tgz +0 -0
- package/components/tryghost-link-redirects-5.80.1.tgz +0 -0
- package/components/tryghost-link-replacer-5.80.1.tgz +0 -0
- package/components/tryghost-link-tracking-5.80.1.tgz +0 -0
- package/components/tryghost-magic-link-5.80.1.tgz +0 -0
- package/components/tryghost-mail-events-5.80.1.tgz +0 -0
- package/components/tryghost-mailgun-client-5.80.1.tgz +0 -0
- package/components/tryghost-member-attribution-5.80.1.tgz +0 -0
- package/components/tryghost-members-api-5.80.1.tgz +0 -0
- package/components/tryghost-members-csv-5.80.1.tgz +0 -0
- package/components/tryghost-members-importer-5.80.1.tgz +0 -0
- package/components/tryghost-members-payments-5.80.1.tgz +0 -0
- package/components/tryghost-members-ssr-5.80.1.tgz +0 -0
- package/components/tryghost-mentions-email-report-5.80.1.tgz +0 -0
- package/components/tryghost-milestones-5.80.1.tgz +0 -0
- package/components/tryghost-minifier-5.80.1.tgz +0 -0
- package/components/tryghost-mw-api-version-mismatch-5.80.1.tgz +0 -0
- package/components/tryghost-mw-cache-control-5.80.1.tgz +0 -0
- package/components/tryghost-mw-version-match-5.80.1.tgz +0 -0
- package/components/tryghost-mw-vhost-5.80.1.tgz +0 -0
- package/components/tryghost-nql-filter-expansions-5.80.1.tgz +0 -0
- package/components/tryghost-oembed-service-5.80.1.tgz +0 -0
- package/components/tryghost-package-json-5.80.1.tgz +0 -0
- package/components/tryghost-post-revisions-5.80.1.tgz +0 -0
- package/components/tryghost-posts-service-5.80.1.tgz +0 -0
- package/components/tryghost-recommendations-5.80.1.tgz +0 -0
- package/components/tryghost-referrers-5.80.1.tgz +0 -0
- package/components/tryghost-session-service-5.80.1.tgz +0 -0
- package/components/tryghost-settings-path-manager-5.80.1.tgz +0 -0
- package/components/tryghost-slack-notifications-5.80.1.tgz +0 -0
- package/components/tryghost-tiers-5.80.1.tgz +0 -0
- package/components/tryghost-verification-trigger-5.80.1.tgz +0 -0
- package/components/tryghost-webmentions-5.80.1.tgz +0 -0
- package/core/built/admin/assets/ghost-40f01556960b49381176bbfa19a89c9a.css +0 -1
- package/core/built/admin/assets/ghost-dark-a86f0499bf2697104213d035da3a3b4d.css +0 -1
- /package/core/built/admin/assets/{chunk.763.6b160e403073ecabbfa6.js.LICENSE.txt → chunk.763.7849440ec6494d2449e9.js.LICENSE.txt} +0 -0
|
@@ -17,6 +17,7 @@ const messages = {
|
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
let JWT_OPTIONS_DEFAULTS = {
|
|
20
|
+
/** @type import('jsonwebtoken').Algorithm[] */
|
|
20
21
|
algorithms: ['HS256'],
|
|
21
22
|
maxAge: '5m'
|
|
22
23
|
};
|
|
@@ -60,7 +61,7 @@ const authenticate = function apiKeyAdminAuth(req, res, next) {
|
|
|
60
61
|
}));
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
return
|
|
64
|
+
return wrappedAuthenticateWithToken(req, res, next, {token});
|
|
64
65
|
};
|
|
65
66
|
|
|
66
67
|
const authenticateWithUrl = function apiKeyAuthenticateWithUrl(req, res, next) {
|
|
@@ -72,9 +73,20 @@ const authenticateWithUrl = function apiKeyAuthenticateWithUrl(req, res, next) {
|
|
|
72
73
|
}));
|
|
73
74
|
}
|
|
74
75
|
// CASE: Scheduler publish URLs can have long maxAge but controllerd by expiry and neverBefore
|
|
75
|
-
return
|
|
76
|
+
return wrappedAuthenticateWithToken(req, res, next, {token, ignoreMaxAge: true});
|
|
76
77
|
};
|
|
77
78
|
|
|
79
|
+
async function wrappedAuthenticateWithToken(req, res, next, options) {
|
|
80
|
+
try {
|
|
81
|
+
const {apiKey, user} = await authenticateWithToken(req.originalUrl, options.token, options.ignoreMaxAge);
|
|
82
|
+
req.api_key = apiKey;
|
|
83
|
+
req.user = user;
|
|
84
|
+
next();
|
|
85
|
+
} catch (err) {
|
|
86
|
+
next(err);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
78
90
|
/**
|
|
79
91
|
* Admin API key authentication flow:
|
|
80
92
|
* 1. extract the JWT token from the `Authorization: Ghost xxxx` header or from URL(for schedules)
|
|
@@ -89,114 +101,104 @@ const authenticateWithUrl = function apiKeyAuthenticateWithUrl(req, res, next) {
|
|
|
89
101
|
* - the "Audience" claim should match the requested API path
|
|
90
102
|
* https://tools.ietf.org/html/rfc7519#section-4.1.3
|
|
91
103
|
*/
|
|
92
|
-
const authenticateWithToken = async function apiKeyAuthenticateWithToken(
|
|
104
|
+
const authenticateWithToken = async function apiKeyAuthenticateWithToken(originalUrl, token, ignoreMaxAge) {
|
|
93
105
|
const decoded = jwt.decode(token, {complete: true});
|
|
106
|
+
const jwtValidationOptions = ignoreMaxAge ? _.omit(JWT_OPTIONS_DEFAULTS, 'maxAge') : JWT_OPTIONS_DEFAULTS;
|
|
94
107
|
|
|
95
108
|
if (!decoded || !decoded.header) {
|
|
96
|
-
|
|
109
|
+
throw new errors.BadRequestError({
|
|
97
110
|
message: tpl(messages.invalidToken),
|
|
98
111
|
code: 'INVALID_JWT'
|
|
99
|
-
})
|
|
112
|
+
});
|
|
100
113
|
}
|
|
101
114
|
|
|
102
115
|
const apiKeyId = decoded.header.kid;
|
|
103
116
|
|
|
104
117
|
if (!apiKeyId) {
|
|
105
|
-
|
|
118
|
+
throw new errors.BadRequestError({
|
|
106
119
|
message: tpl(messages.adminApiKidMissing),
|
|
107
120
|
code: 'MISSING_ADMIN_API_KID'
|
|
108
|
-
})
|
|
121
|
+
});
|
|
109
122
|
}
|
|
110
123
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (limitService.isLimited('customIntegrations')
|
|
124
|
+
const apiKey = await models.ApiKey.findOne({id: apiKeyId}, {withRelated: ['integration']});
|
|
125
|
+
|
|
126
|
+
if (!apiKey) {
|
|
127
|
+
throw new errors.UnauthorizedError({
|
|
128
|
+
message: tpl(messages.unknownAdminApiKey),
|
|
129
|
+
code: 'UNKNOWN_ADMIN_API_KEY'
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (apiKey.get('type') !== 'admin') {
|
|
134
|
+
throw new errors.UnauthorizedError({
|
|
135
|
+
message: tpl(messages.invalidApiKeyType),
|
|
136
|
+
code: 'INVALID_API_KEY_TYPE'
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// CASE: blocking all non-internal: "custom" and "builtin" integration requests when the limit is reached
|
|
141
|
+
if (limitService.isLimited('customIntegrations')
|
|
130
142
|
&& (apiKey.relations.integration && !['internal', 'core'].includes(apiKey.relations.integration.get('type')))) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
// Decoding from hex and transforming into bytes is here to
|
|
137
|
-
// keep comparison of the bytes that are stored in the secret.
|
|
138
|
-
// Useful context:
|
|
139
|
-
// https://github.com/auth0/node-jsonwebtoken/issues/208#issuecomment-231861138
|
|
140
|
-
const secret = Buffer.from(apiKey.get('secret'), 'hex');
|
|
141
|
-
|
|
142
|
-
// Using req.originalUrl means we get the right url even if version-rewrites have happened
|
|
143
|
-
const {version, api} = legacyApiPathMatch(req.originalUrl);
|
|
144
|
-
|
|
145
|
-
// ensure the token was meant for this api
|
|
146
|
-
let options;
|
|
147
|
-
|
|
148
|
-
if (version) {
|
|
149
|
-
// CASE: legacy versioned api request
|
|
150
|
-
options = Object.assign({
|
|
151
|
-
audience: new RegExp(`/?${version}/${api}/?$`)
|
|
152
|
-
}, JWT_OPTIONS);
|
|
153
|
-
} else {
|
|
154
|
-
options = Object.assign({
|
|
155
|
-
audience: new RegExp(`/?${api}/?$`)
|
|
156
|
-
}, JWT_OPTIONS);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
try {
|
|
160
|
-
jwt.verify(token, secret, options);
|
|
161
|
-
} catch (err) {
|
|
162
|
-
if (err.name === 'TokenExpiredError' || err.name === 'JsonWebTokenError') {
|
|
163
|
-
return next(new errors.UnauthorizedError({
|
|
164
|
-
message: tpl(messages.invalidTokenWithMessage, {message: err.message}),
|
|
165
|
-
code: 'INVALID_JWT',
|
|
166
|
-
err
|
|
167
|
-
}));
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// unknown error
|
|
171
|
-
return next(new errors.InternalServerError({err}));
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// authenticated OK
|
|
175
|
-
|
|
176
|
-
if (apiKey.get('user_id')) {
|
|
177
|
-
// fetch the user and store it on the request for later checks and logging
|
|
178
|
-
const user = await models.User.findOne(
|
|
179
|
-
{id: apiKey.get('user_id'), status: 'active'},
|
|
180
|
-
{require: true}
|
|
181
|
-
);
|
|
182
|
-
|
|
183
|
-
req.user = user;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// store the api key on the request for later checks and logging
|
|
187
|
-
req.api_key = apiKey;
|
|
143
|
+
// NOTE: using "checkWouldGoOverLimit" instead of "checkIsOverLimit" here because flag limits don't have
|
|
144
|
+
// a concept of measuring if the limit has been surpassed
|
|
145
|
+
await limitService.errorIfWouldGoOverLimit('customIntegrations');
|
|
146
|
+
}
|
|
188
147
|
|
|
189
|
-
|
|
148
|
+
// Decoding from hex and transforming into bytes is here to
|
|
149
|
+
// keep comparison of the bytes that are stored in the secret.
|
|
150
|
+
// Useful context:
|
|
151
|
+
// https://github.com/auth0/node-jsonwebtoken/issues/208#issuecomment-231861138
|
|
152
|
+
const secret = Buffer.from(apiKey.get('secret'), 'hex');
|
|
153
|
+
|
|
154
|
+
// Using req.originalUrl means we get the right url even if version-rewrites have happened
|
|
155
|
+
const {version, api} = legacyApiPathMatch(originalUrl);
|
|
156
|
+
|
|
157
|
+
// ensure the token was meant for this api
|
|
158
|
+
let options;
|
|
159
|
+
|
|
160
|
+
if (version) {
|
|
161
|
+
// CASE: legacy versioned api request
|
|
162
|
+
options = Object.assign({
|
|
163
|
+
audience: new RegExp(`/?${version}/${api}/?$`)
|
|
164
|
+
}, jwtValidationOptions);
|
|
165
|
+
} else {
|
|
166
|
+
options = Object.assign({
|
|
167
|
+
audience: new RegExp(`/?${api}/?$`)
|
|
168
|
+
}, jwtValidationOptions);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
jwt.verify(token, secret, options);
|
|
190
173
|
} catch (err) {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
174
|
+
throw new errors.UnauthorizedError({
|
|
175
|
+
message: tpl(messages.invalidTokenWithMessage, {message: err.message}),
|
|
176
|
+
code: 'INVALID_JWT',
|
|
177
|
+
err
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// authenticated OK
|
|
182
|
+
let result = {
|
|
183
|
+
user: null,
|
|
184
|
+
apiKey: apiKey
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
if (apiKey.get('user_id')) {
|
|
188
|
+
// fetch the user and store it on the request for later checks and logging
|
|
189
|
+
const user = await models.User.findOne(
|
|
190
|
+
{id: apiKey.get('user_id'), status: 'active'},
|
|
191
|
+
{require: true}
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
result.user = user;
|
|
196
195
|
}
|
|
196
|
+
|
|
197
|
+
return result;
|
|
197
198
|
};
|
|
198
199
|
|
|
199
200
|
module.exports = {
|
|
200
201
|
authenticate,
|
|
201
|
-
authenticateWithUrl
|
|
202
|
+
authenticateWithUrl,
|
|
203
|
+
authenticateWithToken
|
|
202
204
|
};
|
|
@@ -12,10 +12,11 @@ class SlackNotificationsServiceWrapper {
|
|
|
12
12
|
* @param {string} deps.siteUrl
|
|
13
13
|
* @param {boolean} deps.isEnabled
|
|
14
14
|
* @param {URL} deps.webhookUrl
|
|
15
|
+
* @param {number} deps.minThreshold
|
|
15
16
|
*
|
|
16
17
|
* @returns {import('@tryghost/slack-notifications/lib/SlackNotificationsService')}
|
|
17
18
|
*/
|
|
18
|
-
static create({siteUrl, isEnabled, webhookUrl}) {
|
|
19
|
+
static create({siteUrl, isEnabled, webhookUrl, minThreshold}) {
|
|
19
20
|
const {
|
|
20
21
|
SlackNotificationsService,
|
|
21
22
|
SlackNotifications
|
|
@@ -32,7 +33,8 @@ class SlackNotificationsServiceWrapper {
|
|
|
32
33
|
logging,
|
|
33
34
|
config: {
|
|
34
35
|
isEnabled,
|
|
35
|
-
webhookUrl
|
|
36
|
+
webhookUrl,
|
|
37
|
+
minThreshold
|
|
36
38
|
},
|
|
37
39
|
slackNotifications
|
|
38
40
|
});
|
|
@@ -49,8 +51,9 @@ class SlackNotificationsServiceWrapper {
|
|
|
49
51
|
const siteUrl = urlUtils.getSiteUrl();
|
|
50
52
|
const isEnabled = !!(hostSettings?.milestones?.enabled && hostSettings?.milestones?.url);
|
|
51
53
|
const webhookUrl = hostSettings?.milestones?.url;
|
|
54
|
+
const minThreshold = hostSettings?.milestones?.minThreshold ? parseInt(hostSettings.milestones.minThreshold) : 0;
|
|
52
55
|
|
|
53
|
-
this.#api = SlackNotificationsServiceWrapper.create({siteUrl, isEnabled, webhookUrl});
|
|
56
|
+
this.#api = SlackNotificationsServiceWrapper.create({siteUrl, isEnabled, webhookUrl, minThreshold});
|
|
54
57
|
|
|
55
58
|
this.#api.subscribeEvents();
|
|
56
59
|
}
|
|
@@ -3,7 +3,7 @@ const os = require('os');
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const security = require('@tryghost/security');
|
|
5
5
|
const request = require('@tryghost/request');
|
|
6
|
-
const errors = require('@tryghost/errors
|
|
6
|
+
const errors = require('@tryghost/errors');
|
|
7
7
|
const limitService = require('../../services/limits');
|
|
8
8
|
const {setFromZip} = require('./storage');
|
|
9
9
|
|
|
@@ -86,7 +86,8 @@ module.exports = class TierRepository {
|
|
|
86
86
|
* @returns {Promise<import('@tryghost/tiers/lib/Tier')[]>}
|
|
87
87
|
*/
|
|
88
88
|
async getAll(options = {}) {
|
|
89
|
-
const filter = nql(
|
|
89
|
+
const filter = nql();
|
|
90
|
+
filter.filter = options.filter || {};
|
|
90
91
|
return Promise.all(this.#store.slice().filter((item) => {
|
|
91
92
|
return filter.queryJSON(this.toPrimitive(item));
|
|
92
93
|
}).map((tier) => {
|
|
@@ -30,7 +30,24 @@ module.exports = function setupAdminApp() {
|
|
|
30
30
|
}
|
|
31
31
|
));
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
// Auth Frame renders a HTML page that loads some JS which then makes an API
|
|
34
|
+
// request to the Admin API /users/me/ endpoint to check if the user is logged in.
|
|
35
|
+
//
|
|
36
|
+
// Used by comments-ui to add moderation options to front-end comments when logged in.
|
|
37
|
+
adminApp.use('/auth-frame', (req, res, next) => {
|
|
38
|
+
// only render content when we have an Admin session cookie,
|
|
39
|
+
// otherwise return a 204 to avoid JS and API requests being made unnecessarily
|
|
40
|
+
try {
|
|
41
|
+
if (req.headers.cookie?.includes('ghost-admin-api-session')) {
|
|
42
|
+
next();
|
|
43
|
+
} else {
|
|
44
|
+
res.setHeader('Cache-Control', 'public, max-age=0');
|
|
45
|
+
res.sendStatus(204);
|
|
46
|
+
}
|
|
47
|
+
} catch (err) {
|
|
48
|
+
next(err);
|
|
49
|
+
}
|
|
50
|
+
}, serveStatic(
|
|
34
51
|
path.join(config.getContentPath('public'), 'admin-auth')
|
|
35
52
|
));
|
|
36
53
|
|
|
@@ -4,11 +4,13 @@ const bodyParser = require('body-parser');
|
|
|
4
4
|
const errorHandler = require('@tryghost/mw-error-handler');
|
|
5
5
|
const versionMatch = require('@tryghost/mw-version-match');
|
|
6
6
|
|
|
7
|
+
const labs = require('../../../../../shared/labs');
|
|
7
8
|
const shared = require('../../../shared');
|
|
8
9
|
const express = require('../../../../../shared/express');
|
|
9
10
|
const sentry = require('../../../../../shared/sentry');
|
|
10
11
|
const routes = require('./routes');
|
|
11
12
|
const APIVersionCompatibilityService = require('../../../../services/api-version-compatibility');
|
|
13
|
+
const GhostNestApp = require('@tryghost/ghost');
|
|
12
14
|
|
|
13
15
|
module.exports = function setupApiApp() {
|
|
14
16
|
debug('Admin API setup start');
|
|
@@ -33,6 +35,14 @@ module.exports = function setupApiApp() {
|
|
|
33
35
|
// Routing
|
|
34
36
|
apiApp.use(routes());
|
|
35
37
|
|
|
38
|
+
apiApp.use(async (req, res, next) => {
|
|
39
|
+
if (!labs.isSet('NestPlayground')) {
|
|
40
|
+
return next();
|
|
41
|
+
}
|
|
42
|
+
const app = await GhostNestApp.getApp();
|
|
43
|
+
app.getHttpAdapter().getInstance()(req, res, next);
|
|
44
|
+
});
|
|
45
|
+
|
|
36
46
|
// API error handling
|
|
37
47
|
apiApp.use(errorHandler.resourceNotFound);
|
|
38
48
|
apiApp.use(APIVersionCompatibilityService.errorHandler);
|