ghost 4.24.0 → 4.25.0
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/.eslintrc.js +39 -0
- package/content/themes/casper/assets/built/casper.js +1 -1
- package/content/themes/casper/assets/built/casper.js.map +1 -1
- package/content/themes/casper/assets/built/global.css +1 -1
- package/content/themes/casper/assets/built/global.css.map +1 -1
- 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/global.css +6 -1
- package/content/themes/casper/assets/css/screen.css +32 -216
- package/content/themes/casper/default.hbs +2 -2
- package/content/themes/casper/package.json +3 -2
- package/content/themes/casper/post.hbs +1 -1
- package/content/themes/casper/yarn.lock +173 -123
- package/core/boot.js +2 -2
- package/core/built/assets/ghost-dark-d690e732e17ffc794e2e59c1467ca282.css +1 -0
- package/core/built/assets/ghost.min-043bb7480a0810109b130f13b2a4235e.css +1 -0
- package/core/built/assets/{ghost.min-d5595f9c71ebc534ccf9ac78483d357c.js → ghost.min-bc72f685c1c9adc9885925c1412435a5.js} +502 -577
- package/core/built/assets/icons/audio-upload.svg +8 -0
- package/core/built/assets/{vendor.min-1a84ac3ef74edf31c6e86810b45221cc.js → vendor.min-d1234c632a54502777c34e50752fa3fc.js} +2598 -2137
- package/core/frontend/apps/amp/lib/helpers/amp_content.js +2 -2
- package/core/frontend/apps/amp/lib/views/amp.hbs +8 -0
- package/core/frontend/apps/private-blogging/index.js +1 -1
- package/core/frontend/services/apps/index.js +1 -1
- package/core/frontend/services/apps/loader.js +3 -3
- package/core/frontend/services/helpers/handlebars.js +1 -1
- package/core/frontend/services/theme-engine/middleware/ensure-active-theme.js +34 -0
- package/core/frontend/services/theme-engine/middleware/index.js +6 -0
- package/core/frontend/services/theme-engine/middleware/update-global-template-options.js +116 -0
- package/core/frontend/services/theme-engine/middleware/update-local-template-data.js +9 -0
- package/core/frontend/services/theme-engine/middleware/update-local-template-options.js +57 -0
- package/core/frontend/src/cards/css/button.css +4 -0
- package/core/frontend/src/cards/css/callout.css +10 -10
- package/core/frontend/web/middleware/error-handler.js +93 -0
- package/core/frontend/web/middleware/handle-image-sizes.js +3 -6
- package/core/frontend/web/middleware/index.js +1 -0
- package/core/frontend/web/site.js +1 -1
- package/core/server/adapters/scheduling/SchedulingDefault.js +2 -2
- package/core/server/adapters/storage/LocalStorageBase.js +2 -2
- package/core/server/api/canary/db.js +2 -2
- package/core/server/api/canary/media.js +3 -2
- package/core/server/api/canary/oembed.js +16 -1
- package/core/server/api/canary/session.js +1 -1
- package/core/server/api/canary/slugs.js +1 -1
- package/core/server/api/canary/utils/permissions.js +2 -2
- package/core/server/api/canary/utils/serializers/output/config.js +2 -6
- package/core/server/api/v2/db.js +2 -2
- package/core/server/api/v2/session.js +1 -1
- package/core/server/api/v2/slugs.js +1 -1
- package/core/server/api/v2/utils/permissions.js +2 -2
- package/core/server/api/v3/db.js +2 -2
- package/core/server/api/v3/session.js +1 -1
- package/core/server/api/v3/slugs.js +1 -1
- package/core/server/api/v3/utils/permissions.js +2 -2
- package/core/server/data/db/state-manager.js +4 -4
- package/core/server/data/exporter/export-filename.js +1 -1
- package/core/server/data/importer/handlers/json.js +1 -1
- package/core/server/data/importer/import-manager.js +1 -1
- package/core/server/data/importer/importers/data/base.js +1 -1
- package/core/server/data/migrations/utils.js +2 -2
- package/core/server/data/migrations/versions/1.25/1-update-koenig-beta-html.js +1 -0
- package/core/server/data/migrations/versions/3.1/08-add-uuid-values-to-members.js +1 -0
- package/core/server/data/migrations/versions/3.22/02-settings-key-renames.js +2 -0
- package/core/server/data/migrations/versions/3.22/05-migrate-members-subscription-settings.js +3 -0
- package/core/server/data/migrations/versions/3.22/06-migrate-stripe-connect-settings.js +2 -0
- package/core/server/data/migrations/versions/3.23/01-migrate-bulk-email-settings.js +1 -0
- package/core/server/data/migrations/versions/3.29/01-remove-duplicate-subscriptions.js +2 -0
- package/core/server/data/migrations/versions/3.29/02-remove-duplicate-customers.js +2 -0
- package/core/server/data/migrations/versions/3.38/04-populate-recipient-filter-column.js +2 -0
- package/core/server/data/migrations/versions/4.0/01-update-mobiledoc.js +2 -0
- package/core/server/data/migrations/versions/4.0/03-populate-status-column-for-members.js +4 -0
- package/core/server/data/migrations/versions/4.0/06-populate-members-subscribe-events-table.js +1 -0
- package/core/server/data/migrations/versions/4.0/17-populate-members-status-events-table.js +1 -0
- package/core/server/data/migrations/versions/4.0/18-transform-urls-absolute-to-transform-ready.js +5 -0
- package/core/server/data/migrations/versions/4.0/22-solve-orphaned-webhooks.js +1 -0
- package/core/server/data/migrations/versions/4.0/23-regenerate-posts-html.js +1 -0
- package/core/server/data/migrations/versions/4.0/25-populate-members-paid-subscription-events-table.js +2 -1
- package/core/server/data/migrations/versions/4.12/02-fix-member-statuses.js +1 -0
- package/core/server/data/migrations/versions/4.14/01-fix-comped-member-statuses.js +3 -0
- package/core/server/data/migrations/versions/4.14/02-fix-free-members-status-events.js +1 -0
- package/core/server/data/migrations/versions/4.20/05-remove-not-null-constraint-from-portal-title.js +2 -0
- package/core/server/data/migrations/versions/4.23/01-truncate-offer-names.js +1 -0
- package/core/server/data/migrations/versions/4.3/04-attach-members-to-product.js +1 -0
- package/core/server/data/migrations/versions/4.4/01-restore-free-members-signup-setting-from-backup.js +1 -0
- package/core/server/data/migrations/versions/4.6/01-remove-comped-status.js +1 -0
- package/core/server/data/migrations/versions/4.8/04-migrate-show-newsletter-header-setting.js +1 -0
- package/core/server/data/migrations/versions/4.9/05-fix-missed-mobiledoc-url-transforms.js +1 -0
- package/core/server/data/migrations/versions/4.9/06-add-comped-status.js +1 -0
- package/core/server/data/migrations/versions/4.9/07-update-comped-members-status-events.js +1 -0
- package/core/server/data/schema/commands.js +2 -2
- package/core/server/ghost-server.js +2 -2
- package/core/server/lib/image/image-size.js +2 -2
- package/core/server/models/base/listeners.js +2 -2
- package/core/server/models/member-email-change-event.js +2 -2
- package/core/server/models/member-login-event.js +2 -2
- package/core/server/models/member-paid-subscription-event.js +3 -3
- package/core/server/models/member-payment-event.js +3 -3
- package/core/server/models/member-product-event.js +6 -6
- package/core/server/models/member-status-event.js +5 -3
- package/core/server/models/member-subscribe-event.js +9 -3
- package/core/server/models/relations/authors.js +1 -1
- package/core/server/models/settings.js +1 -1
- package/core/server/services/auth/passwordreset.js +1 -1
- package/core/server/services/auth/setup.js +1 -1
- package/core/server/services/mega/mega.js +6 -4
- package/core/server/services/mega/template.js +12 -12
- package/core/server/services/members/api.js +22 -0
- package/core/server/services/members/config.js +1 -1
- package/core/server/services/members/emails/signup-paid.js +168 -0
- package/core/server/services/members/service.js +6 -2
- package/core/server/services/members/stripe-connect.js +4 -2
- package/core/server/services/nft-oembed.js +6 -1
- package/core/server/services/oembed.js +6 -2
- package/core/server/services/permissions/can-this.js +1 -1
- package/core/server/services/redirects/api.js +2 -2
- package/core/server/services/route-settings/default-settings-manager.js +1 -1
- package/core/server/services/route-settings/route-settings.js +4 -12
- package/core/server/services/route-settings/settings-loader.js +4 -4
- package/core/server/services/route-settings/yaml-parser.js +1 -1
- package/core/server/services/slack.js +1 -1
- package/core/server/services/themes/storage.js +2 -2
- package/core/server/services/twitter-embed.js +80 -0
- package/core/server/services/xmlrpc.js +2 -2
- package/core/server/web/admin/views/default-prod.html +4 -4
- package/core/server/web/admin/views/default.html +4 -4
- package/core/server/web/api/canary/admin/middleware.js +1 -1
- package/core/server/web/api/v2/admin/middleware.js +1 -1
- package/core/server/web/api/v3/admin/middleware.js +1 -1
- package/core/server/web/shared/middleware/error-handler.js +28 -150
- package/core/shared/config/defaults.json +7 -1
- package/core/shared/labs.js +3 -4
- package/core/shared/sentry.js +1 -1
- package/package.json +11 -11
- package/yarn.lock +426 -731
- package/content/themes/casper/assets/js/gallery-card.js +0 -24
- package/core/built/assets/ghost-dark-e7b57ab951512c5719aee89b16b9a448.css +0 -1
- package/core/built/assets/ghost.min-7f3603dbeb5ebf0ec09e207ae82fb4e3.css +0 -1
- package/core/frontend/services/theme-engine/middleware.js +0 -209
|
@@ -128,7 +128,7 @@ CanThisResult.prototype.beginCheck = function (context) {
|
|
|
128
128
|
context = parseContext(context);
|
|
129
129
|
|
|
130
130
|
if (actionsMap.empty()) {
|
|
131
|
-
throw new errors.
|
|
131
|
+
throw new errors.InternalServerError({message: tpl(messages.noActionsMapFoundError)});
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
// Kick off loading of user permissions if necessary
|
|
@@ -34,7 +34,7 @@ const readRedirectsFile = async (redirectsPath) => {
|
|
|
34
34
|
return '';
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
if (errors.utils.
|
|
37
|
+
if (errors.utils.isGhostError(err)) {
|
|
38
38
|
throw err;
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -162,7 +162,7 @@ class CustomRedirectsAPI {
|
|
|
162
162
|
}
|
|
163
163
|
}
|
|
164
164
|
} catch (err) {
|
|
165
|
-
if (errors.utils.
|
|
165
|
+
if (errors.utils.isGhostError(err)) {
|
|
166
166
|
logging.error(err);
|
|
167
167
|
} else {
|
|
168
168
|
logging.error(new errors.IncorrectUsageError({
|
|
@@ -48,7 +48,7 @@ class DefaultSettingsManager {
|
|
|
48
48
|
});
|
|
49
49
|
}).catch((error) => {
|
|
50
50
|
// CASE: we might have a permission error, as we can't access the directory
|
|
51
|
-
throw new errors.
|
|
51
|
+
throw new errors.InternalServerError({
|
|
52
52
|
message: tpl(messages.ensureSettings, {
|
|
53
53
|
path: this.destinationFolderPath
|
|
54
54
|
}),
|
|
@@ -9,7 +9,7 @@ const tpl = require('@tryghost/tpl');
|
|
|
9
9
|
const bridge = require('../../../bridge');
|
|
10
10
|
|
|
11
11
|
const messages = {
|
|
12
|
-
loadError: 'Could not load
|
|
12
|
+
loadError: 'Could not load routes.yaml file.'
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
/**
|
|
@@ -38,14 +38,6 @@ class RouteSettings {
|
|
|
38
38
|
* @private
|
|
39
39
|
*/
|
|
40
40
|
this.defaultRoutesSettingHash = '3d180d52c663d173a6be791ef411ed01';
|
|
41
|
-
/**
|
|
42
|
-
* @private
|
|
43
|
-
*/
|
|
44
|
-
this.filename = 'routes';
|
|
45
|
-
/**
|
|
46
|
-
* @private
|
|
47
|
-
*/
|
|
48
|
-
this.ext = 'yaml';
|
|
49
41
|
|
|
50
42
|
this.settingsLoader = settingsLoader;
|
|
51
43
|
this.settingsPath = settingsPath;
|
|
@@ -90,7 +82,7 @@ class RouteSettings {
|
|
|
90
82
|
return Promise.resolve([]);
|
|
91
83
|
}
|
|
92
84
|
|
|
93
|
-
if (errors.utils.
|
|
85
|
+
if (errors.utils.isGhostError(err)) {
|
|
94
86
|
throw err;
|
|
95
87
|
}
|
|
96
88
|
|
|
@@ -115,7 +107,7 @@ class RouteSettings {
|
|
|
115
107
|
};
|
|
116
108
|
|
|
117
109
|
try {
|
|
118
|
-
bridge.reloadFrontend();
|
|
110
|
+
await bridge.reloadFrontend();
|
|
119
111
|
} catch (err) {
|
|
120
112
|
return bringBackValidRoutes()
|
|
121
113
|
.finally(() => {
|
|
@@ -134,7 +126,7 @@ class RouteSettings {
|
|
|
134
126
|
if (!urlService.hasFinished()) {
|
|
135
127
|
if (tries > 5) {
|
|
136
128
|
throw new errors.InternalServerError({
|
|
137
|
-
message: tpl(messages.loadError
|
|
129
|
+
message: tpl(messages.loadError)
|
|
138
130
|
});
|
|
139
131
|
}
|
|
140
132
|
|
|
@@ -36,11 +36,11 @@ class SettingsLoader {
|
|
|
36
36
|
|
|
37
37
|
return validate(object);
|
|
38
38
|
} catch (err) {
|
|
39
|
-
if (errors.utils.
|
|
39
|
+
if (errors.utils.isGhostError(err)) {
|
|
40
40
|
throw err;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
throw new errors.
|
|
43
|
+
throw new errors.InternalServerError({
|
|
44
44
|
message: tpl(messages.settingsLoaderError, {
|
|
45
45
|
setting: 'routes',
|
|
46
46
|
path: this.settingFilePath
|
|
@@ -66,11 +66,11 @@ class SettingsLoader {
|
|
|
66
66
|
|
|
67
67
|
return validate(object);
|
|
68
68
|
} catch (err) {
|
|
69
|
-
if (errors.utils.
|
|
69
|
+
if (errors.utils.isGhostError(err)) {
|
|
70
70
|
throw err;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
throw new errors.
|
|
73
|
+
throw new errors.InternalServerError({
|
|
74
74
|
message: tpl(messages.settingsLoaderError, {
|
|
75
75
|
setting: 'routes',
|
|
76
76
|
path: this.settingFilePath
|
|
@@ -136,7 +136,7 @@ function ping(post) {
|
|
|
136
136
|
'Content-type': 'application/json'
|
|
137
137
|
}
|
|
138
138
|
}).catch(function (err) {
|
|
139
|
-
logging.error(new errors.
|
|
139
|
+
logging.error(new errors.InternalServerError({
|
|
140
140
|
err: err,
|
|
141
141
|
context: tpl(messages.requestFailedError, {service: 'slack'}),
|
|
142
142
|
help: tpl(messages.requestFailedHelp, {url: 'https://ghost.org/docs/'})
|
|
@@ -112,7 +112,7 @@ module.exports = {
|
|
|
112
112
|
if (checkedTheme) {
|
|
113
113
|
fs.remove(checkedTheme.path)
|
|
114
114
|
.catch((err) => {
|
|
115
|
-
logging.error(new errors.
|
|
115
|
+
logging.error(new errors.InternalServerError({err: err}));
|
|
116
116
|
});
|
|
117
117
|
}
|
|
118
118
|
|
|
@@ -120,7 +120,7 @@ module.exports = {
|
|
|
120
120
|
getStorage()
|
|
121
121
|
.delete(backupName)
|
|
122
122
|
.catch((err) => {
|
|
123
|
-
logging.error(new errors.
|
|
123
|
+
logging.error(new errors.InternalServerError({err: err}));
|
|
124
124
|
});
|
|
125
125
|
}
|
|
126
126
|
},
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const {extract} = require('oembed-parser');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import('./oembed').ICustomProvider} ICustomProvider
|
|
5
|
+
* @typedef {import('./oembed').IExternalRequest} IExternalRequest
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const TWITTER_PATH_REGEX = /\/status\/(\d+)/;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @implements ICustomProvider
|
|
12
|
+
*/
|
|
13
|
+
class TwitterOEmbedProvider {
|
|
14
|
+
/**
|
|
15
|
+
* @param {object} dependencies
|
|
16
|
+
*/
|
|
17
|
+
constructor(dependencies) {
|
|
18
|
+
this.dependencies = dependencies;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {URL} url
|
|
23
|
+
* @returns {Promise<boolean>}
|
|
24
|
+
*/
|
|
25
|
+
async canSupportRequest(url) {
|
|
26
|
+
return url.host === 'twitter.com' && TWITTER_PATH_REGEX.test(url.pathname);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {URL} url
|
|
31
|
+
* @param {IExternalRequest} externalRequest
|
|
32
|
+
*
|
|
33
|
+
* @returns {Promise<object>}
|
|
34
|
+
*/
|
|
35
|
+
async getOEmbedData(url, externalRequest) {
|
|
36
|
+
const [match, tweetId] = url.pathname.match(TWITTER_PATH_REGEX);
|
|
37
|
+
if (!match) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** @type {object} */
|
|
42
|
+
const oembedData = await extract(url.href);
|
|
43
|
+
|
|
44
|
+
if (this.dependencies.config.bearerToken) {
|
|
45
|
+
const query = {
|
|
46
|
+
expansions: ['attachments.poll_ids', 'attachments.media_keys', 'author_id', 'entities.mentions.username', 'geo.place_id', 'in_reply_to_user_id', 'referenced_tweets.id', 'referenced_tweets.id.author_id'],
|
|
47
|
+
'media.fields': ['duration_ms', 'height', 'media_key', 'preview_image_url', 'type', 'url', 'width', 'public_metrics', 'alt_text'],
|
|
48
|
+
'place.fields': ['contained_within', 'country', 'country_code', 'full_name', 'geo', 'id', 'name', 'place_type'],
|
|
49
|
+
'poll.fields': ['duration_minutes', 'end_datetime', 'id', 'options', 'voting_status'],
|
|
50
|
+
'tweet.fields': ['attachments', 'author_id', 'context_annotations', 'conversation_id', 'created_at', 'entities', 'geo', 'id', 'in_reply_to_user_id', 'lang', 'public_metrics', 'possibly_sensitive', 'referenced_tweets', 'reply_settings', 'source', 'text', 'withheld'],
|
|
51
|
+
'user.fields': ['created_at', 'description', 'entities', 'id', 'location', 'name', 'pinned_tweet_id', 'profile_image_url', 'protected', 'public_metrics', 'url', 'username', 'verified', 'withheld']
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const queryString = Object.keys(query).map((key) => {
|
|
55
|
+
return `${key}=${query[key].join(',')}`;
|
|
56
|
+
}).join('&');
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const result = await externalRequest(`https://api.twitter.com/2/tweets/${tweetId}?${queryString}`, {
|
|
60
|
+
responseType: 'json',
|
|
61
|
+
headers: {
|
|
62
|
+
Authorization: `Bearer ${this.dependencies.config.bearerToken}`
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const body = JSON.parse(result.body);
|
|
67
|
+
|
|
68
|
+
oembedData.tweet_data = body.data;
|
|
69
|
+
} catch (err) {
|
|
70
|
+
this.dependencies.logging.error(err);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
oembedData.type = 'twitter';
|
|
75
|
+
|
|
76
|
+
return oembedData;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = TwitterOEmbedProvider;
|
|
@@ -87,7 +87,7 @@ function ping(post) {
|
|
|
87
87
|
if (!goodResponse.test(res.body)) {
|
|
88
88
|
const matches = res.body.match(errorMessage);
|
|
89
89
|
const message = matches ? matches[1] : res.body;
|
|
90
|
-
throw new errors.
|
|
90
|
+
throw new errors.InternalServerError({message});
|
|
91
91
|
}
|
|
92
92
|
})
|
|
93
93
|
.catch(function (err) {
|
|
@@ -100,7 +100,7 @@ function ping(post) {
|
|
|
100
100
|
help: tpl(messages.requestFailedHelp, {url: 'https://ghost.org/docs/'})
|
|
101
101
|
});
|
|
102
102
|
} else {
|
|
103
|
-
error = new errors.
|
|
103
|
+
error = new errors.InternalServerError({
|
|
104
104
|
err: err,
|
|
105
105
|
message: err.message,
|
|
106
106
|
context: tpl(messages.requestFailedError, {service: 'xmlrpc'}),
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<title>Ghost Admin</title>
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
<meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%2F%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%224.
|
|
11
|
+
<meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%2F%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%224.25%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22moment%22%3A%7B%22includeTimezone%22%3A%22all%22%7D%2C%22emberKeyboard%22%3A%7B%22disableInputsInitializer%22%3Atrue%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%7D" />
|
|
12
12
|
|
|
13
13
|
<meta name="HandheldFriendly" content="True" />
|
|
14
14
|
<meta name="MobileOptimized" content="320" />
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
<link rel="stylesheet" href="assets/vendor.min-987af30228885bce50f05c4723fe6f53.css">
|
|
44
|
-
<link rel="stylesheet" href="assets/ghost.min-
|
|
44
|
+
<link rel="stylesheet" href="assets/ghost.min-043bb7480a0810109b130f13b2a4235e.css" title="light">
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
|
|
@@ -59,8 +59,8 @@
|
|
|
59
59
|
<div id="ember-basic-dropdown-wormhole"></div>
|
|
60
60
|
|
|
61
61
|
|
|
62
|
-
<script src="assets/vendor.min-
|
|
63
|
-
<script src="assets/ghost.min-
|
|
62
|
+
<script src="assets/vendor.min-d1234c632a54502777c34e50752fa3fc.js"></script>
|
|
63
|
+
<script src="assets/ghost.min-bc72f685c1c9adc9885925c1412435a5.js"></script>
|
|
64
64
|
|
|
65
65
|
</body>
|
|
66
66
|
</html>
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<title>Ghost Admin</title>
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
<meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%2F%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%224.
|
|
11
|
+
<meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%2F%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%224.25%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22moment%22%3A%7B%22includeTimezone%22%3A%22all%22%7D%2C%22emberKeyboard%22%3A%7B%22disableInputsInitializer%22%3Atrue%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%7D" />
|
|
12
12
|
|
|
13
13
|
<meta name="HandheldFriendly" content="True" />
|
|
14
14
|
<meta name="MobileOptimized" content="320" />
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
<link rel="stylesheet" href="assets/vendor.min-987af30228885bce50f05c4723fe6f53.css">
|
|
44
|
-
<link rel="stylesheet" href="assets/ghost.min-
|
|
44
|
+
<link rel="stylesheet" href="assets/ghost.min-043bb7480a0810109b130f13b2a4235e.css" title="light">
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
|
|
@@ -59,8 +59,8 @@
|
|
|
59
59
|
<div id="ember-basic-dropdown-wormhole"></div>
|
|
60
60
|
|
|
61
61
|
|
|
62
|
-
<script src="assets/vendor.min-
|
|
63
|
-
<script src="assets/ghost.min-
|
|
62
|
+
<script src="assets/vendor.min-d1234c632a54502777c34e50752fa3fc.js"></script>
|
|
63
|
+
<script src="assets/ghost.min-bc72f685c1c9adc9885925c1412435a5.js"></script>
|
|
64
64
|
|
|
65
65
|
</body>
|
|
66
66
|
</html>
|
|
@@ -1,16 +1,10 @@
|
|
|
1
|
-
const hbs = require('express-hbs');
|
|
2
1
|
const _ = require('lodash');
|
|
3
2
|
const debug = require('@tryghost/debug')('error-handler');
|
|
4
3
|
const errors = require('@tryghost/errors');
|
|
5
4
|
const tpl = require('@tryghost/tpl');
|
|
6
|
-
const config = require('../../../../shared/config');
|
|
7
|
-
const helpers = require('../../../../frontend/services/routing/helpers');
|
|
8
5
|
const sentry = require('../../../../shared/sentry');
|
|
9
6
|
|
|
10
7
|
const messages = {
|
|
11
|
-
oopsErrorTemplateHasError: 'Oops, seems there is an error in the error template.',
|
|
12
|
-
encounteredError: 'Encountered the error: ',
|
|
13
|
-
whilstTryingToRender: 'whilst trying to render an error page for the error: ',
|
|
14
8
|
pageNotFound: 'Page not found',
|
|
15
9
|
resourceNotFound: 'Resource not found',
|
|
16
10
|
actions: {
|
|
@@ -45,22 +39,7 @@ const messages = {
|
|
|
45
39
|
}
|
|
46
40
|
};
|
|
47
41
|
|
|
48
|
-
const
|
|
49
|
-
const _private = {};
|
|
50
|
-
const errorHandler = {};
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* This is a bare minimum setup, which allows us to render the error page
|
|
54
|
-
* It uses the {{asset}} helper, and nothing more
|
|
55
|
-
*/
|
|
56
|
-
_private.createHbsEngine = () => {
|
|
57
|
-
const engine = hbs.create();
|
|
58
|
-
engine.registerHelper('asset', require('../../../../frontend/helpers/asset'));
|
|
59
|
-
|
|
60
|
-
return engine.express4();
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
_private.updateStack = (err) => {
|
|
42
|
+
const updateStack = (err) => {
|
|
64
43
|
let stackbits = err.stack.split(/\n/g);
|
|
65
44
|
|
|
66
45
|
// We build this up backwards, so we always insert at position 1
|
|
@@ -88,25 +67,30 @@ _private.updateStack = (err) => {
|
|
|
88
67
|
|
|
89
68
|
/**
|
|
90
69
|
* Get an error ready to be shown the the user
|
|
91
|
-
*
|
|
92
|
-
* @TODO: support multiple errors within one single error, see https://github.com/TryGhost/Ghost/issues/7116#issuecomment-252231809
|
|
93
70
|
*/
|
|
94
|
-
|
|
71
|
+
module.exports.prepareError = (err, req, res, next) => {
|
|
95
72
|
debug(err);
|
|
96
73
|
|
|
97
74
|
if (Array.isArray(err)) {
|
|
98
75
|
err = err[0];
|
|
99
76
|
}
|
|
100
77
|
|
|
101
|
-
if (!errors.utils.
|
|
78
|
+
if (!errors.utils.isGhostError(err)) {
|
|
102
79
|
// We need a special case for 404 errors
|
|
103
|
-
// @TODO look at adding this to the GhostError class
|
|
104
80
|
if (err.statusCode && err.statusCode === 404) {
|
|
105
81
|
err = new errors.NotFoundError({
|
|
106
82
|
err: err
|
|
107
83
|
});
|
|
84
|
+
} else if (err.stack.match(/node_modules\/handlebars\//)) {
|
|
85
|
+
// Temporary handling of theme errors from handlebars
|
|
86
|
+
// @TODO remove this when #10496 is solved properly
|
|
87
|
+
err = new errors.IncorrectUsageError({
|
|
88
|
+
err: err,
|
|
89
|
+
message: err.message,
|
|
90
|
+
statusCode: err.statusCode
|
|
91
|
+
});
|
|
108
92
|
} else {
|
|
109
|
-
err = new errors.
|
|
93
|
+
err = new errors.InternalServerError({
|
|
110
94
|
err: err,
|
|
111
95
|
message: err.message,
|
|
112
96
|
statusCode: err.statusCode
|
|
@@ -120,7 +104,7 @@ _private.prepareError = (err, req, res, next) => {
|
|
|
120
104
|
// alternative for res.status();
|
|
121
105
|
res.statusCode = err.statusCode;
|
|
122
106
|
|
|
123
|
-
err.stack =
|
|
107
|
+
err.stack = updateStack(err);
|
|
124
108
|
|
|
125
109
|
// never cache errors
|
|
126
110
|
res.set({
|
|
@@ -130,7 +114,7 @@ _private.prepareError = (err, req, res, next) => {
|
|
|
130
114
|
next(err);
|
|
131
115
|
};
|
|
132
116
|
|
|
133
|
-
|
|
117
|
+
const jsonErrorRenderer = (err, req, res, next) => { // eslint-disable-line no-unused-vars
|
|
134
118
|
res.json({
|
|
135
119
|
errors: [{
|
|
136
120
|
message: err.message,
|
|
@@ -143,8 +127,8 @@ _private.JSONErrorRenderer = (err, req, res, next) => { // eslint-disable-line n
|
|
|
143
127
|
});
|
|
144
128
|
};
|
|
145
129
|
|
|
146
|
-
|
|
147
|
-
const userError =
|
|
130
|
+
const jsonErrorRendererV2 = (err, req, res, next) => { // eslint-disable-line no-unused-vars
|
|
131
|
+
const userError = prepareUserMessage(err, req);
|
|
148
132
|
|
|
149
133
|
res.json({
|
|
150
134
|
errors: [{
|
|
@@ -160,7 +144,7 @@ _private.JSONErrorRendererV2 = (err, req, res, next) => { // eslint-disable-line
|
|
|
160
144
|
});
|
|
161
145
|
};
|
|
162
146
|
|
|
163
|
-
|
|
147
|
+
const prepareUserMessage = (err, res) => {
|
|
164
148
|
const userError = {
|
|
165
149
|
message: err.message,
|
|
166
150
|
context: err.context
|
|
@@ -206,141 +190,35 @@ _private.prepareUserMessage = (err, res) => {
|
|
|
206
190
|
return userError;
|
|
207
191
|
};
|
|
208
192
|
|
|
209
|
-
|
|
210
|
-
<p>${tpl(messages.encounteredError)}</p>
|
|
211
|
-
<pre>${escapeExpression(err.message || err)}</pre>
|
|
212
|
-
<br ><p>${tpl(messages.whilstTryingToRender)}</p>
|
|
213
|
-
${err.statusCode} <pre>${escapeExpression(err.message || err)}</pre>`;
|
|
214
|
-
|
|
215
|
-
_private.ThemeErrorRenderer = (err, req, res, next) => {
|
|
216
|
-
// If the error code is explicitly set to STATIC_FILE_NOT_FOUND,
|
|
217
|
-
// Skip trying to render an HTML error, and move on to the basic error renderer
|
|
218
|
-
// We do this because customised 404 templates could reference the image that's missing
|
|
219
|
-
// A better long term solution might be to do this based on extension
|
|
220
|
-
if (err.code === 'STATIC_FILE_NOT_FOUND') {
|
|
221
|
-
return next(err);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Renderer begin
|
|
225
|
-
// Format Data
|
|
226
|
-
const data = {
|
|
227
|
-
message: err.message,
|
|
228
|
-
// @deprecated Remove in Ghost 5.0
|
|
229
|
-
code: err.statusCode,
|
|
230
|
-
statusCode: err.statusCode,
|
|
231
|
-
errorDetails: err.errorDetails || []
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
// Template
|
|
235
|
-
// @TODO: very dirty !!!!!!
|
|
236
|
-
helpers.templates.setTemplate(req, res);
|
|
237
|
-
|
|
238
|
-
// It can be that something went wrong with the theme or otherwise loading handlebars
|
|
239
|
-
// This ensures that no matter what res.render will work here
|
|
240
|
-
// @TODO: split the error handler for assets, admin & theme to refactor this away
|
|
241
|
-
if (_.isEmpty(req.app.engines)) {
|
|
242
|
-
res._template = 'error';
|
|
243
|
-
req.app.engine('hbs', _private.createHbsEngine());
|
|
244
|
-
req.app.set('view engine', 'hbs');
|
|
245
|
-
req.app.set('views', config.get('paths').defaultViews);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// @TODO use renderer here?!
|
|
249
|
-
// Render Call - featuring an error handler for what happens if rendering fails
|
|
250
|
-
res.render(res._template, data, (_err, html) => {
|
|
251
|
-
if (!_err) {
|
|
252
|
-
return res.send(html);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// re-attach new error e.g. error template has syntax error or misusage
|
|
256
|
-
req.err = _err;
|
|
257
|
-
|
|
258
|
-
// And then try to explain things to the user...
|
|
259
|
-
// Cheat and output the error using handlebars escapeExpression
|
|
260
|
-
return res.status(500).send(_private.ErrorFallbackMessage(_err));
|
|
261
|
-
});
|
|
262
|
-
};
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* Borrowed heavily from finalHandler
|
|
266
|
-
*/
|
|
267
|
-
|
|
268
|
-
const DOUBLE_SPACE_REGEXP = /\x20{2}/g;
|
|
269
|
-
const NEWLINE_REGEXP = /\n/g;
|
|
270
|
-
|
|
271
|
-
function createHtmlDocument(status, message) {
|
|
272
|
-
let body = escapeExpression(message)
|
|
273
|
-
.replace(NEWLINE_REGEXP, '<br>')
|
|
274
|
-
.replace(DOUBLE_SPACE_REGEXP, ' ');
|
|
275
|
-
|
|
276
|
-
return `<!DOCTYPE html>\n
|
|
277
|
-
<html lang="en">\n
|
|
278
|
-
<head>\n
|
|
279
|
-
<meta charset="utf-8">\n
|
|
280
|
-
<title>${status} Error</title>\n
|
|
281
|
-
</head>\n
|
|
282
|
-
<body>\n
|
|
283
|
-
<pre>${status} ${body}</pre>\n
|
|
284
|
-
</body>\n
|
|
285
|
-
</html>\n`;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
_private.HTMLErrorRenderer = (err, req, res, next) => { // eslint-disable-line no-unused-vars
|
|
289
|
-
return res.send(createHtmlDocument(res.statusCode, err.stack));
|
|
290
|
-
};
|
|
291
|
-
|
|
292
|
-
_private.BasicErrorRenderer = (err, req, res, next) => { // eslint-disable-line no-unused-vars
|
|
293
|
-
return res.send(res.statusCode + ' ' + err.stack);
|
|
294
|
-
};
|
|
295
|
-
|
|
296
|
-
errorHandler.resourceNotFound = (req, res, next) => {
|
|
297
|
-
// TODO, handle unknown resources & methods differently, so that we can also produce
|
|
298
|
-
// 405 Method Not Allowed
|
|
193
|
+
module.exports.resourceNotFound = (req, res, next) => {
|
|
299
194
|
next(new errors.NotFoundError({message: tpl(messages.resourceNotFound)}));
|
|
300
195
|
};
|
|
301
196
|
|
|
302
|
-
|
|
197
|
+
module.exports.pageNotFound = (req, res, next) => {
|
|
303
198
|
next(new errors.NotFoundError({message: tpl(messages.pageNotFound)}));
|
|
304
199
|
};
|
|
305
200
|
|
|
306
|
-
|
|
201
|
+
module.exports.handleJSONResponse = [
|
|
307
202
|
// Make sure the error can be served
|
|
308
|
-
|
|
203
|
+
module.exports.prepareError,
|
|
309
204
|
// Handle the error in Sentry
|
|
310
205
|
sentry.errorHandler,
|
|
311
206
|
// Render the error using JSON format
|
|
312
|
-
|
|
207
|
+
jsonErrorRenderer
|
|
313
208
|
];
|
|
314
209
|
|
|
315
|
-
|
|
210
|
+
module.exports.handleJSONResponseV2 = [
|
|
316
211
|
// Make sure the error can be served
|
|
317
|
-
|
|
212
|
+
module.exports.prepareError,
|
|
318
213
|
// Handle the error in Sentry
|
|
319
214
|
sentry.errorHandler,
|
|
320
215
|
// Render the error using JSON format
|
|
321
|
-
|
|
216
|
+
jsonErrorRendererV2
|
|
322
217
|
];
|
|
323
218
|
|
|
324
|
-
|
|
219
|
+
module.exports.handleHTMLResponse = [
|
|
325
220
|
// Make sure the error can be served
|
|
326
|
-
|
|
221
|
+
module.exports.prepareError,
|
|
327
222
|
// Handle the error in Sentry
|
|
328
|
-
sentry.errorHandler
|
|
329
|
-
// Render the error using HTML format
|
|
330
|
-
_private.HTMLErrorRenderer,
|
|
331
|
-
// Fall back to basic if HTML is not explicitly accepted
|
|
332
|
-
_private.BasicErrorRenderer
|
|
223
|
+
sentry.errorHandler
|
|
333
224
|
];
|
|
334
|
-
|
|
335
|
-
errorHandler.handleThemeResponse = [
|
|
336
|
-
// Make sure the error can be served
|
|
337
|
-
_private.prepareError,
|
|
338
|
-
// Handle the error in Sentry
|
|
339
|
-
sentry.errorHandler,
|
|
340
|
-
// Render the error using theme template
|
|
341
|
-
_private.ThemeErrorRenderer,
|
|
342
|
-
// Fall back to basic if HTML is not explicitly accepted
|
|
343
|
-
_private.BasicErrorRenderer
|
|
344
|
-
];
|
|
345
|
-
|
|
346
|
-
module.exports = errorHandler;
|
|
@@ -131,7 +131,13 @@
|
|
|
131
131
|
"version": "1.12"
|
|
132
132
|
},
|
|
133
133
|
"tenor": {
|
|
134
|
-
"
|
|
134
|
+
"publicReadOnlyApiKey": null,
|
|
135
135
|
"contentFilter": "off"
|
|
136
|
+
},
|
|
137
|
+
"opensea": {
|
|
138
|
+
"privateReadOnlyApiKey": null
|
|
139
|
+
},
|
|
140
|
+
"twitter": {
|
|
141
|
+
"privateReadOnlyToken": null
|
|
136
142
|
}
|
|
137
143
|
}
|
package/core/shared/labs.js
CHANGED
|
@@ -15,7 +15,8 @@ const messages = {
|
|
|
15
15
|
|
|
16
16
|
// flags in this list always return `true`, allows quick global enable prior to full flag removal
|
|
17
17
|
const GA_FEATURES = [
|
|
18
|
-
'customThemeSettings'
|
|
18
|
+
'customThemeSettings',
|
|
19
|
+
'nftCard'
|
|
19
20
|
];
|
|
20
21
|
|
|
21
22
|
// NOTE: this allowlist is meant to be used to filter out any unexpected
|
|
@@ -33,11 +34,8 @@ const ALPHA_FEATURES = [
|
|
|
33
34
|
'mediaAPI',
|
|
34
35
|
'filesAPI',
|
|
35
36
|
'membersAutoLogin',
|
|
36
|
-
'buttonCard',
|
|
37
37
|
'calloutCard',
|
|
38
|
-
'nftCard',
|
|
39
38
|
'accordionCard',
|
|
40
|
-
'gifsCard',
|
|
41
39
|
'fileCard',
|
|
42
40
|
'audioCard',
|
|
43
41
|
'videoCard',
|
|
@@ -107,6 +105,7 @@ module.exports.enabledHelper = function enabledHelper(options, callback) {
|
|
|
107
105
|
});
|
|
108
106
|
errDetails.help = tpl(options.errorHelp || messages.errorHelp, {url: options.helpUrl});
|
|
109
107
|
|
|
108
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
110
109
|
logging.error(new errors.DisabledFeatureError(errDetails));
|
|
111
110
|
|
|
112
111
|
const {SafeString} = require('express-hbs');
|