ghost 4.22.0 → 4.22.4
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/.c8rc.json +24 -0
- package/.eslintrc.js +6 -0
- package/Gruntfile.js +1 -1
- package/content/public/README.md +3 -0
- package/core/boot.js +20 -12
- package/core/built/assets/{chunk.3.1148677ff3b78e5aeaee.js → chunk.3.8f95b516d88ff4eec64c.js} +18 -18
- package/core/built/assets/{ghost-dark-684ad238e1a858c7cb5be6988de7c6f5.css → ghost-dark-42cf6e0c730578940ec069bda45aea41.css} +1 -1
- package/core/built/assets/{ghost.min-efbfb823467b66f4acc66537d033aa55.js → ghost.min-2e3e64eb258cf424c59c3e308b4bc6e6.js} +215 -155
- package/core/built/assets/{ghost.min-66e08535f8bb797a8c40e0a2b31f1e9e.css → ghost.min-fcf6a0738421f86c47c55f20d00c5ba9.css} +1 -1
- package/core/built/assets/icons/powered-by-tenor.svg +35 -0
- package/core/built/assets/icons/tenor.svg +7 -0
- package/core/built/assets/{vendor.min-7c8fdd90f7ecd2e94328a07ea3b64608.js → vendor.min-c9002845b6c30ac978abdadde9f33d7c.js} +8189 -7601
- package/core/frontend/apps/amp/lib/views/amp.hbs +104 -0
- package/core/frontend/apps/private-blogging/lib/router.js +1 -1
- package/core/frontend/services/card-assets/service.js +21 -13
- package/core/frontend/services/routing/CollectionRouter.js +4 -5
- package/core/frontend/services/routing/EmailRouter.js +1 -1
- package/core/frontend/services/routing/ParentRouter.js +0 -8
- package/core/frontend/services/routing/PreviewRouter.js +1 -1
- package/core/frontend/services/routing/StaticPagesRouter.js +1 -1
- package/core/frontend/services/routing/StaticRoutesRouter.js +4 -4
- package/core/frontend/services/routing/TaxonomyRouter.js +3 -3
- package/core/frontend/services/routing/{middlewares → middleware}/index.js +0 -0
- package/core/frontend/services/routing/{middlewares → middleware}/page-param.js +0 -0
- package/core/frontend/services/routing/router-manager.js +7 -2
- package/core/frontend/services/rss/generate-feed.js +2 -1
- package/core/frontend/src/cards/css/bookmark.css +66 -48
- package/core/frontend/src/cards/css/button.css +30 -0
- package/core/frontend/src/cards/css/callout.css +50 -0
- package/core/frontend/src/cards/css/gallery.css +8 -13
- package/core/frontend/src/cards/css/nft.css +94 -0
- package/core/frontend/src/cards/css/toggle.css +47 -0
- package/core/frontend/src/cards/js/toggle.js +16 -0
- package/core/frontend/web/middleware/serve-public-file.js +14 -8
- package/core/frontend/web/routes.js +0 -1
- package/core/frontend/web/site.js +15 -12
- package/core/server/adapters/storage/LocalFilesStorage.js +17 -0
- package/core/server/adapters/storage/LocalImagesStorage.js +1 -0
- package/core/server/adapters/storage/LocalMediaStorage.js +2 -1
- package/core/server/adapters/storage/LocalStorageBase.js +30 -5
- package/core/server/api/canary/authentication.js +1 -1
- package/core/server/api/canary/files.js +19 -0
- package/core/server/api/canary/index.js +4 -0
- package/core/server/api/canary/media.js +25 -5
- package/core/server/api/canary/oembed.js +3 -0
- package/core/server/api/canary/utils/serializers/input/index.js +4 -0
- package/core/server/api/canary/utils/serializers/input/media.js +8 -0
- package/core/server/api/canary/utils/serializers/output/config.js +21 -14
- package/core/server/api/canary/utils/serializers/output/files.js +27 -0
- package/core/server/api/canary/utils/serializers/output/index.js +4 -0
- package/core/server/api/canary/utils/serializers/output/media.js +9 -0
- package/core/server/api/canary/utils/validators/input/files.js +7 -0
- package/core/server/api/canary/utils/validators/input/index.js +4 -0
- package/core/server/api/canary/utils/validators/input/media.js +4 -0
- package/core/server/api/v2/authentication.js +1 -1
- package/core/server/api/v3/authentication.js +1 -1
- package/core/server/data/db/connection.js +7 -0
- package/core/server/data/importer/importers/data/data-importer.js +3 -3
- package/core/server/data/migrations/init/2-create-fixtures.js +3 -20
- package/core/server/data/migrations/versions/1.21/1-add-contributor-role.js +5 -5
- package/core/server/data/migrations/versions/2.15/2-insert-zapier-integration.js +3 -3
- package/core/server/data/migrations/versions/2.2/3-insert-admin-integration-role.js +5 -5
- package/core/server/data/migrations/versions/2.27/1-insert-ghost-db-backup-role.js +5 -6
- package/core/server/data/migrations/versions/2.27/2-insert-db-backup-integration.js +3 -4
- package/core/server/data/migrations/versions/2.28/3-insert-ghost-scheduler-role.js +7 -7
- package/core/server/data/migrations/versions/2.28/4-insert-scheduler-integration.js +3 -3
- package/core/server/data/schema/fixtures/fixture-manager.js +340 -0
- package/core/server/data/schema/fixtures/index.js +8 -2
- package/core/server/services/mega/post-email-serializer.js +5 -1
- package/core/server/services/mega/segment-parser.js +1 -2
- package/core/server/services/mega/template.js +69 -1
- package/core/server/services/nft-oembed.js +57 -0
- package/core/server/services/oembed.js +149 -110
- package/core/server/services/public-config/config.js +2 -1
- package/core/server/services/stripe/index.js +4 -2
- package/core/server/services/url/Resource.js +1 -1
- package/core/server/services/url/Resources.js +36 -23
- package/core/server/services/url/UrlGenerator.js +23 -20
- package/core/server/services/url/UrlService.js +123 -21
- package/core/server/services/url/Urls.js +7 -2
- package/core/server/services/url/index.js +9 -1
- package/core/server/web/admin/app.js +6 -6
- package/core/server/web/admin/views/default-prod.html +3 -3
- package/core/server/web/admin/views/default.html +3 -3
- package/core/server/web/api/app.js +1 -1
- package/core/server/web/api/canary/admin/app.js +4 -4
- package/core/server/web/api/canary/admin/middleware.js +6 -6
- package/core/server/web/api/canary/admin/routes.js +20 -5
- package/core/server/web/api/canary/content/app.js +4 -4
- package/core/server/web/api/canary/content/middleware.js +3 -3
- package/core/server/web/api/middleware/cors.js +7 -7
- package/core/server/web/api/v2/admin/app.js +4 -4
- package/core/server/web/api/v2/admin/middleware.js +6 -6
- package/core/server/web/api/v2/admin/routes.js +5 -5
- package/core/server/web/api/v2/content/app.js +4 -4
- package/core/server/web/api/v2/content/middleware.js +3 -3
- package/core/server/web/api/v3/admin/app.js +4 -4
- package/core/server/web/api/v3/admin/middleware.js +6 -6
- package/core/server/web/api/v3/admin/routes.js +5 -5
- package/core/server/web/api/v3/content/app.js +4 -4
- package/core/server/web/api/v3/content/middleware.js +3 -3
- package/core/server/web/members/app.js +7 -7
- package/core/server/web/oauth/app.js +1 -1
- package/core/server/web/parent/app.js +2 -3
- package/core/server/web/parent/frontend.js +1 -1
- package/core/server/web/shared/index.js +2 -2
- package/core/server/web/shared/{middlewares → middleware}/api/index.js +0 -0
- package/core/server/web/shared/{middlewares → middleware}/api/spam-prevention.js +0 -0
- package/core/server/web/shared/{middlewares → middleware}/brute.js +0 -0
- package/core/server/web/shared/{middlewares → middleware}/cache-control.js +0 -0
- package/core/server/web/shared/{middlewares → middleware}/error-handler.js +0 -0
- package/core/server/web/shared/{middlewares → middleware}/index.js +0 -0
- package/core/server/web/shared/{middlewares → middleware}/maintenance.js +0 -0
- package/core/server/web/shared/{middlewares → middleware}/pretty-urls.js +0 -0
- package/core/server/web/shared/{middlewares → middleware}/uncapitalise.js +0 -0
- package/core/server/web/shared/{middlewares → middleware}/url-redirects.js +0 -0
- package/core/shared/config/defaults.json +10 -2
- package/core/shared/config/helpers.js +44 -0
- package/core/shared/config/loader.js +1 -1
- package/core/shared/config/overrides.json +2 -2
- package/core/shared/labs.js +8 -1
- package/loggingrc.js +19 -20
- package/package.json +36 -36
- package/urls.json +597 -0
- package/yarn.lock +666 -354
- package/core/server/data/schema/fixtures/utils.js +0 -321
- package/core/server/web/parent/vhost-utils.js +0 -39
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
1
2
|
const _debug = require('@tryghost/debug')._base;
|
|
2
3
|
const debug = _debug('ghost:services:url:service');
|
|
3
4
|
const _ = require('lodash');
|
|
4
5
|
const errors = require('@tryghost/errors');
|
|
6
|
+
const labs = require('../../../shared/labs');
|
|
5
7
|
const UrlGenerator = require('./UrlGenerator');
|
|
6
8
|
const Queue = require('./Queue');
|
|
7
9
|
const Urls = require('./Urls');
|
|
@@ -17,15 +19,28 @@ const events = require('../../lib/common/events');
|
|
|
17
19
|
* It will tell you if the url generation is in progress or not.
|
|
18
20
|
*/
|
|
19
21
|
class UrlService {
|
|
20
|
-
|
|
22
|
+
/**
|
|
23
|
+
*
|
|
24
|
+
* @param {Object} options
|
|
25
|
+
* @param {String} [options.urlsCachePath] - cached URLs storage path
|
|
26
|
+
* @param {String} [options.resourcesCachePath] - cached resources storage path
|
|
27
|
+
*/
|
|
28
|
+
constructor({urlsCachePath, resourcesCachePath} = {}) {
|
|
21
29
|
this.utils = urlUtils;
|
|
22
|
-
|
|
30
|
+
this.urlsCachePath = urlsCachePath;
|
|
31
|
+
this.resourcesCachePath = resourcesCachePath;
|
|
32
|
+
this.onFinished = null;
|
|
23
33
|
this.finished = false;
|
|
24
34
|
this.urlGenerators = [];
|
|
25
35
|
|
|
26
|
-
|
|
36
|
+
// Get urls
|
|
27
37
|
this.queue = new Queue();
|
|
28
|
-
|
|
38
|
+
// NOTE: Urls and Resources should not be initialized here but only in the init method.
|
|
39
|
+
// Way too many tests fail if the initialization is removed so leaving it as is for time being
|
|
40
|
+
this.urls = new Urls();
|
|
41
|
+
this.resources = new Resources({
|
|
42
|
+
queue: this.queue
|
|
43
|
+
});
|
|
29
44
|
|
|
30
45
|
this._listeners();
|
|
31
46
|
}
|
|
@@ -68,26 +83,41 @@ class UrlService {
|
|
|
68
83
|
_onQueueEnded(event) {
|
|
69
84
|
if (event === 'init') {
|
|
70
85
|
this.finished = true;
|
|
86
|
+
if (this.onFinished) {
|
|
87
|
+
this.onFinished();
|
|
88
|
+
}
|
|
71
89
|
}
|
|
72
90
|
}
|
|
73
91
|
|
|
74
92
|
/**
|
|
75
93
|
* @description Router was created, connect it with a url generator.
|
|
76
|
-
* @param {
|
|
94
|
+
* @param {String} identifier frontend router ID reference
|
|
95
|
+
* @param {String} filter NQL filter
|
|
96
|
+
* @param {String} resourceType
|
|
97
|
+
* @param {String} permalink
|
|
77
98
|
*/
|
|
78
|
-
onRouterAddedType(
|
|
79
|
-
debug('Registering route: ',
|
|
80
|
-
|
|
81
|
-
let urlGenerator = new UrlGenerator(
|
|
99
|
+
onRouterAddedType(identifier, filter, resourceType, permalink) {
|
|
100
|
+
debug('Registering route: ', filter, resourceType, permalink);
|
|
101
|
+
|
|
102
|
+
let urlGenerator = new UrlGenerator({
|
|
103
|
+
identifier,
|
|
104
|
+
filter,
|
|
105
|
+
resourceType,
|
|
106
|
+
permalink,
|
|
107
|
+
queue: this.queue,
|
|
108
|
+
resources: this.resources,
|
|
109
|
+
urls: this.urls,
|
|
110
|
+
position: this.urlGenerators.length
|
|
111
|
+
});
|
|
82
112
|
this.urlGenerators.push(urlGenerator);
|
|
83
113
|
}
|
|
84
114
|
|
|
85
115
|
/**
|
|
86
116
|
* @description Router update handler - regenerates it's resources
|
|
87
|
-
* @param {
|
|
117
|
+
* @param {String} identifier router ID linked to the UrlGenerator
|
|
88
118
|
*/
|
|
89
|
-
onRouterUpdated(
|
|
90
|
-
const generator = this.urlGenerators.find(g => g.
|
|
119
|
+
onRouterUpdated(identifier) {
|
|
120
|
+
const generator = this.urlGenerators.find(g => g.identifier === identifier);
|
|
91
121
|
generator.regenerateResources();
|
|
92
122
|
}
|
|
93
123
|
|
|
@@ -246,7 +276,7 @@ class UrlService {
|
|
|
246
276
|
let urlGenerator;
|
|
247
277
|
|
|
248
278
|
this.urlGenerators.every((_urlGenerator) => {
|
|
249
|
-
if (_urlGenerator.
|
|
279
|
+
if (_urlGenerator.identifier === routerId) {
|
|
250
280
|
urlGenerator = _urlGenerator;
|
|
251
281
|
return false;
|
|
252
282
|
}
|
|
@@ -276,18 +306,90 @@ class UrlService {
|
|
|
276
306
|
return null;
|
|
277
307
|
}
|
|
278
308
|
|
|
279
|
-
|
|
280
|
-
|
|
309
|
+
const permalink = _.find(this.urlGenerators, {uid: object.generatorId}).permalink;
|
|
310
|
+
|
|
311
|
+
if (options.withUrlOptions) {
|
|
312
|
+
return urlUtils.urlJoin(permalink, '/:options(edit)?/');
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return permalink;
|
|
281
316
|
}
|
|
282
317
|
|
|
283
318
|
/**
|
|
284
|
-
* @description
|
|
285
|
-
*
|
|
286
|
-
* @
|
|
287
|
-
* because this service get's initalised via events.
|
|
319
|
+
* @description Initializes components needed for the URL Service to function
|
|
320
|
+
* @param {Object} options
|
|
321
|
+
* @param {Function} [options.onFinished] - callback when url generation is finished
|
|
288
322
|
*/
|
|
289
|
-
init() {
|
|
290
|
-
this.
|
|
323
|
+
async init(options = {}) {
|
|
324
|
+
this.onFinished = options.onFinished;
|
|
325
|
+
|
|
326
|
+
let persistedUrls;
|
|
327
|
+
let persistedResources;
|
|
328
|
+
|
|
329
|
+
if (labs.isSet('urlCache')) {
|
|
330
|
+
persistedUrls = await this.readCacheFile(this.urlsCachePath);
|
|
331
|
+
persistedResources = await this.readCacheFile(this.resourcesCachePath);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (persistedUrls && persistedResources) {
|
|
335
|
+
this.urls = new Urls({
|
|
336
|
+
urls: persistedUrls
|
|
337
|
+
});
|
|
338
|
+
this.resources = new Resources({
|
|
339
|
+
queue: this.queue,
|
|
340
|
+
resources: persistedResources
|
|
341
|
+
});
|
|
342
|
+
this.resources.initResourceConfig();
|
|
343
|
+
this.resources.initEvenListeners();
|
|
344
|
+
|
|
345
|
+
this._onQueueEnded('init');
|
|
346
|
+
} else {
|
|
347
|
+
this.resources.initResourceConfig();
|
|
348
|
+
this.resources.initEvenListeners();
|
|
349
|
+
await this.resources.fetchResources();
|
|
350
|
+
// CASE: all resources are fetched, start the queue
|
|
351
|
+
this.queue.start({
|
|
352
|
+
event: 'init',
|
|
353
|
+
tolerance: 100,
|
|
354
|
+
requiredSubscriberCount: 1
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async shutdown() {
|
|
360
|
+
if (!labs.isSet('urlCache')) {
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
await this.persistToCacheFile(this.urlsCachePath, this.urls.urls);
|
|
365
|
+
await this.persistToCacheFile(this.resourcesCachePath, this.resources.getAll());
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
async persistToCacheFile(filePath, data) {
|
|
369
|
+
return fs.writeFile(filePath, JSON.stringify(data, null, 4));
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
async readCacheFile(filePath) {
|
|
373
|
+
let cacheExists = false;
|
|
374
|
+
let cacheData;
|
|
375
|
+
|
|
376
|
+
try {
|
|
377
|
+
await fs.stat(filePath);
|
|
378
|
+
cacheExists = true;
|
|
379
|
+
} catch (e) {
|
|
380
|
+
cacheExists = false;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (cacheExists) {
|
|
384
|
+
try {
|
|
385
|
+
const cacheFile = await fs.readFile(filePath, 'utf8');
|
|
386
|
+
cacheData = JSON.parse(cacheFile);
|
|
387
|
+
} catch (e) {
|
|
388
|
+
//noop as we'd start a long boot process if there are any errors in the file
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return cacheData;
|
|
291
393
|
}
|
|
292
394
|
|
|
293
395
|
/**
|
|
@@ -20,8 +20,13 @@ const events = require('../../lib/common/events');
|
|
|
20
20
|
* You can easily ask `this.urls[resourceId]`.
|
|
21
21
|
*/
|
|
22
22
|
class Urls {
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
/**
|
|
24
|
+
*
|
|
25
|
+
* @param {Object} [options]
|
|
26
|
+
* @param {Object} [options.urls] map of available URLs with their resources
|
|
27
|
+
*/
|
|
28
|
+
constructor({urls = {}} = {}) {
|
|
29
|
+
this.urls = urls;
|
|
25
30
|
}
|
|
26
31
|
|
|
27
32
|
/**
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const config = require('../../../shared/config');
|
|
1
3
|
const UrlService = require('./UrlService');
|
|
2
|
-
|
|
4
|
+
|
|
5
|
+
// NOTE: instead of a path we could give UrlService a "data-resolver" of some sort
|
|
6
|
+
// so it doesn't have to contain the logic to read data at all. This would be
|
|
7
|
+
// a possible improvement in the future
|
|
8
|
+
const urlsCachePath = path.join(config.getContentPath('data'), 'urls.json');
|
|
9
|
+
const resourcesCachePath = path.join(config.getContentPath('data'), 'resources.json');
|
|
10
|
+
const urlService = new UrlService({urlsCachePath, resourcesCachePath});
|
|
3
11
|
|
|
4
12
|
// Singleton
|
|
5
13
|
module.exports = urlService;
|
|
@@ -27,19 +27,19 @@ module.exports = function setupAdminApp() {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
// Render error page in case of maintenance
|
|
30
|
-
adminApp.use(shared.
|
|
30
|
+
adminApp.use(shared.middleware.maintenance);
|
|
31
31
|
|
|
32
32
|
// Force SSL if required
|
|
33
33
|
// must happen AFTER asset loading and BEFORE routing
|
|
34
|
-
adminApp.use(shared.
|
|
34
|
+
adminApp.use(shared.middleware.urlRedirects.adminSSLAndHostRedirect);
|
|
35
35
|
|
|
36
36
|
// Add in all trailing slashes & remove uppercase
|
|
37
37
|
// must happen AFTER asset loading and BEFORE routing
|
|
38
|
-
adminApp.use(shared.
|
|
38
|
+
adminApp.use(shared.middleware.prettyUrls);
|
|
39
39
|
|
|
40
40
|
// Cache headers go last before serving the request
|
|
41
41
|
// Admin is currently set to not be cached at all
|
|
42
|
-
adminApp.use(shared.
|
|
42
|
+
adminApp.use(shared.middleware.cacheControl('private'));
|
|
43
43
|
|
|
44
44
|
// Special redirects for the admin (these should have their own cache-control headers)
|
|
45
45
|
adminApp.use(adminMiddleware);
|
|
@@ -47,8 +47,8 @@ module.exports = function setupAdminApp() {
|
|
|
47
47
|
// Finally, routing
|
|
48
48
|
adminApp.get('*', require('./controller'));
|
|
49
49
|
|
|
50
|
-
adminApp.use(shared.
|
|
51
|
-
adminApp.use(shared.
|
|
50
|
+
adminApp.use(shared.middleware.errorHandler.pageNotFound);
|
|
51
|
+
adminApp.use(shared.middleware.errorHandler.handleHTMLResponse);
|
|
52
52
|
|
|
53
53
|
debug('Admin setup end');
|
|
54
54
|
|
|
@@ -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-fcf6a0738421f86c47c55f20d00c5ba9.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-c9002845b6c30ac978abdadde9f33d7c.js"></script>
|
|
63
|
+
<script src="assets/ghost.min-2e3e64eb258cf424c59c3e308b4bc6e6.js"></script>
|
|
64
64
|
|
|
65
65
|
</body>
|
|
66
66
|
</html>
|
|
@@ -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-fcf6a0738421f86c47c55f20d00c5ba9.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-c9002845b6c30ac978abdadde9f33d7c.js"></script>
|
|
63
|
+
<script src="assets/ghost.min-2e3e64eb258cf424c59c3e308b4bc6e6.js"></script>
|
|
64
64
|
|
|
65
65
|
</body>
|
|
66
66
|
</html>
|
|
@@ -2,7 +2,7 @@ const debug = require('@tryghost/debug')('web:api:default:app');
|
|
|
2
2
|
const config = require('../../../shared/config');
|
|
3
3
|
const express = require('../../../shared/express');
|
|
4
4
|
const urlUtils = require('../../../shared/url-utils');
|
|
5
|
-
const errorHandler = require('../shared/
|
|
5
|
+
const errorHandler = require('../shared/middleware/error-handler');
|
|
6
6
|
|
|
7
7
|
module.exports = function setupApiApp() {
|
|
8
8
|
debug('Parent API setup start');
|
|
@@ -20,21 +20,21 @@ module.exports = function setupApiApp() {
|
|
|
20
20
|
apiApp.use(boolParser());
|
|
21
21
|
|
|
22
22
|
// send 503 json response in case of maintenance
|
|
23
|
-
apiApp.use(shared.
|
|
23
|
+
apiApp.use(shared.middleware.maintenance);
|
|
24
24
|
|
|
25
25
|
// Check version matches for API requests, depends on res.locals.safeVersion being set
|
|
26
26
|
// Therefore must come after themeHandler.ghostLocals, for now
|
|
27
27
|
apiApp.use(apiMw.versionMatch);
|
|
28
28
|
|
|
29
29
|
// Admin API shouldn't be cached
|
|
30
|
-
apiApp.use(shared.
|
|
30
|
+
apiApp.use(shared.middleware.cacheControl('private'));
|
|
31
31
|
|
|
32
32
|
// Routing
|
|
33
33
|
apiApp.use(routes());
|
|
34
34
|
|
|
35
35
|
// API error handling
|
|
36
|
-
apiApp.use(shared.
|
|
37
|
-
apiApp.use(shared.
|
|
36
|
+
apiApp.use(shared.middleware.errorHandler.resourceNotFound);
|
|
37
|
+
apiApp.use(shared.middleware.errorHandler.handleJSONResponseV2);
|
|
38
38
|
|
|
39
39
|
debug('Admin API canary setup end');
|
|
40
40
|
|
|
@@ -59,8 +59,8 @@ module.exports.authAdminApi = [
|
|
|
59
59
|
auth.authorize.authorizeAdminApi,
|
|
60
60
|
apiMw.updateUserLastSeen,
|
|
61
61
|
apiMw.cors,
|
|
62
|
-
shared.
|
|
63
|
-
shared.
|
|
62
|
+
shared.middleware.urlRedirects.adminSSLAndHostRedirect,
|
|
63
|
+
shared.middleware.prettyUrls,
|
|
64
64
|
notImplemented
|
|
65
65
|
];
|
|
66
66
|
|
|
@@ -73,8 +73,8 @@ module.exports.authAdminApiWithUrl = [
|
|
|
73
73
|
auth.authorize.authorizeAdminApi,
|
|
74
74
|
apiMw.updateUserLastSeen,
|
|
75
75
|
apiMw.cors,
|
|
76
|
-
shared.
|
|
77
|
-
shared.
|
|
76
|
+
shared.middleware.urlRedirects.adminSSLAndHostRedirect,
|
|
77
|
+
shared.middleware.prettyUrls,
|
|
78
78
|
notImplemented
|
|
79
79
|
];
|
|
80
80
|
|
|
@@ -83,7 +83,7 @@ module.exports.authAdminApiWithUrl = [
|
|
|
83
83
|
*/
|
|
84
84
|
module.exports.publicAdminApi = [
|
|
85
85
|
apiMw.cors,
|
|
86
|
-
shared.
|
|
87
|
-
shared.
|
|
86
|
+
shared.middleware.urlRedirects.adminSSLAndHostRedirect,
|
|
87
|
+
shared.middleware.prettyUrls,
|
|
88
88
|
notImplemented
|
|
89
89
|
];
|
|
@@ -204,8 +204,8 @@ module.exports = function apiRoutes() {
|
|
|
204
204
|
router.get('/session', mw.authAdminApi, http(api.session.read));
|
|
205
205
|
// We don't need auth when creating a new session (logging in)
|
|
206
206
|
router.post('/session',
|
|
207
|
-
shared.
|
|
208
|
-
shared.
|
|
207
|
+
shared.middleware.brute.globalBlock,
|
|
208
|
+
shared.middleware.brute.userLogin,
|
|
209
209
|
http(api.session.add)
|
|
210
210
|
);
|
|
211
211
|
router.del('/session', mw.authAdminApi, http(api.session.delete));
|
|
@@ -215,11 +215,11 @@ module.exports = function apiRoutes() {
|
|
|
215
215
|
|
|
216
216
|
// ## Authentication
|
|
217
217
|
router.post('/authentication/passwordreset',
|
|
218
|
-
shared.
|
|
219
|
-
shared.
|
|
218
|
+
shared.middleware.brute.globalReset,
|
|
219
|
+
shared.middleware.brute.userReset,
|
|
220
220
|
http(api.authentication.generateResetToken)
|
|
221
221
|
);
|
|
222
|
-
router.put('/authentication/passwordreset', shared.
|
|
222
|
+
router.put('/authentication/passwordreset', shared.middleware.brute.globalBlock, http(api.authentication.resetPassword));
|
|
223
223
|
router.post('/authentication/invitation', http(api.authentication.acceptInvitation));
|
|
224
224
|
router.get('/authentication/invitation', http(api.authentication.isInvitation));
|
|
225
225
|
router.post('/authentication/setup', http(api.authentication.setup));
|
|
@@ -244,6 +244,21 @@ module.exports = function apiRoutes() {
|
|
|
244
244
|
apiMw.upload.mediaValidation({type: 'media'}),
|
|
245
245
|
http(api.media.upload)
|
|
246
246
|
);
|
|
247
|
+
router.put('/media/thumbnail/upload',
|
|
248
|
+
labs.enabledMiddleware('mediaAPI'),
|
|
249
|
+
mw.authAdminApi,
|
|
250
|
+
apiMw.upload.single('file'),
|
|
251
|
+
apiMw.upload.validation({type: 'images'}),
|
|
252
|
+
http(api.media.uploadThumbnail)
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
// ## files
|
|
256
|
+
router.post('/files/upload',
|
|
257
|
+
labs.enabledMiddleware('filesAPI'),
|
|
258
|
+
mw.authAdminApi,
|
|
259
|
+
apiMw.upload.single('file'),
|
|
260
|
+
http(api.files.upload)
|
|
261
|
+
);
|
|
247
262
|
|
|
248
263
|
// ## Invites
|
|
249
264
|
router.get('/invites', mw.authAdminApi, http(api.invites.browse));
|
|
@@ -18,17 +18,17 @@ module.exports = function setupApiApp() {
|
|
|
18
18
|
apiApp.use(boolParser());
|
|
19
19
|
|
|
20
20
|
// send 503 json response in case of maintenance
|
|
21
|
-
apiApp.use(shared.
|
|
21
|
+
apiApp.use(shared.middleware.maintenance);
|
|
22
22
|
|
|
23
23
|
// API shouldn't be cached
|
|
24
|
-
apiApp.use(shared.
|
|
24
|
+
apiApp.use(shared.middleware.cacheControl('private'));
|
|
25
25
|
|
|
26
26
|
// Routing
|
|
27
27
|
apiApp.use(routes());
|
|
28
28
|
|
|
29
29
|
// API error handling
|
|
30
|
-
apiApp.use(shared.
|
|
31
|
-
apiApp.use(shared.
|
|
30
|
+
apiApp.use(shared.middleware.errorHandler.resourceNotFound);
|
|
31
|
+
apiApp.use(shared.middleware.errorHandler.handleJSONResponse);
|
|
32
32
|
|
|
33
33
|
debug('Content API canary setup end');
|
|
34
34
|
|
|
@@ -14,10 +14,10 @@ const shared = require('../../../shared');
|
|
|
14
14
|
* Authentication for public endpoints
|
|
15
15
|
*/
|
|
16
16
|
module.exports.authenticatePublic = [
|
|
17
|
-
shared.
|
|
17
|
+
shared.middleware.brute.contentApiKey,
|
|
18
18
|
auth.authenticate.authenticateContentApi,
|
|
19
19
|
auth.authorize.authorizeContentApi,
|
|
20
20
|
cors(),
|
|
21
|
-
shared.
|
|
22
|
-
shared.
|
|
21
|
+
shared.middleware.urlRedirects.adminSSLAndHostRedirect,
|
|
22
|
+
shared.middleware.prettyUrls
|
|
23
23
|
];
|
|
@@ -3,7 +3,7 @@ const url = require('url');
|
|
|
3
3
|
const os = require('os');
|
|
4
4
|
const urlUtils = require('../../../../shared/url-utils');
|
|
5
5
|
|
|
6
|
-
let
|
|
6
|
+
let allowlist = [];
|
|
7
7
|
const ENABLE_CORS = {origin: true, maxAge: 86400};
|
|
8
8
|
const DISABLE_CORS = {origin: false};
|
|
9
9
|
|
|
@@ -46,16 +46,16 @@ function getUrls() {
|
|
|
46
46
|
return urls;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
function
|
|
49
|
+
function getAllowlist() {
|
|
50
50
|
// This needs doing just one time after init
|
|
51
|
-
if (
|
|
51
|
+
if (allowlist.length === 0) {
|
|
52
52
|
// origins that always match: localhost, local IPs, etc.
|
|
53
|
-
|
|
53
|
+
allowlist = allowlist.concat(getIPs());
|
|
54
54
|
// Trusted urls from config.js
|
|
55
|
-
|
|
55
|
+
allowlist = allowlist.concat(getUrls());
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
return
|
|
58
|
+
return allowlist;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
/**
|
|
@@ -73,7 +73,7 @@ function handleCORS(req, cb) {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
// Origin matches whitelist
|
|
76
|
-
if (
|
|
76
|
+
if (getAllowlist().indexOf(url.parse(origin).hostname) > -1) {
|
|
77
77
|
return cb(null, ENABLE_CORS);
|
|
78
78
|
}
|
|
79
79
|
|
|
@@ -20,21 +20,21 @@ module.exports = function setupApiApp() {
|
|
|
20
20
|
apiApp.use(boolParser());
|
|
21
21
|
|
|
22
22
|
// send 503 json response in case of maintenance
|
|
23
|
-
apiApp.use(shared.
|
|
23
|
+
apiApp.use(shared.middleware.maintenance);
|
|
24
24
|
|
|
25
25
|
// Check version matches for API requests, depends on res.locals.safeVersion being set
|
|
26
26
|
// Therefore must come after themeHandler.ghostLocals, for now
|
|
27
27
|
apiApp.use(apiMw.versionMatch);
|
|
28
28
|
|
|
29
29
|
// Admin API shouldn't be cached
|
|
30
|
-
apiApp.use(shared.
|
|
30
|
+
apiApp.use(shared.middleware.cacheControl('private'));
|
|
31
31
|
|
|
32
32
|
// Routing
|
|
33
33
|
apiApp.use(routes());
|
|
34
34
|
|
|
35
35
|
// API error handling
|
|
36
|
-
apiApp.use(shared.
|
|
37
|
-
apiApp.use(shared.
|
|
36
|
+
apiApp.use(shared.middleware.errorHandler.resourceNotFound);
|
|
37
|
+
apiApp.use(shared.middleware.errorHandler.handleJSONResponseV2);
|
|
38
38
|
|
|
39
39
|
debug('Admin API v2 setup end');
|
|
40
40
|
|
|
@@ -55,8 +55,8 @@ module.exports.authAdminApi = [
|
|
|
55
55
|
auth.authorize.authorizeAdminApi,
|
|
56
56
|
apiMw.updateUserLastSeen,
|
|
57
57
|
apiMw.cors,
|
|
58
|
-
shared.
|
|
59
|
-
shared.
|
|
58
|
+
shared.middleware.urlRedirects.adminSSLAndHostRedirect,
|
|
59
|
+
shared.middleware.prettyUrls,
|
|
60
60
|
notImplemented
|
|
61
61
|
];
|
|
62
62
|
|
|
@@ -69,8 +69,8 @@ module.exports.authAdminApiWithUrl = [
|
|
|
69
69
|
auth.authorize.authorizeAdminApi,
|
|
70
70
|
apiMw.updateUserLastSeen,
|
|
71
71
|
apiMw.cors,
|
|
72
|
-
shared.
|
|
73
|
-
shared.
|
|
72
|
+
shared.middleware.urlRedirects.adminSSLAndHostRedirect,
|
|
73
|
+
shared.middleware.prettyUrls,
|
|
74
74
|
notImplemented
|
|
75
75
|
];
|
|
76
76
|
|
|
@@ -79,7 +79,7 @@ module.exports.authAdminApiWithUrl = [
|
|
|
79
79
|
*/
|
|
80
80
|
module.exports.publicAdminApi = [
|
|
81
81
|
apiMw.cors,
|
|
82
|
-
shared.
|
|
83
|
-
shared.
|
|
82
|
+
shared.middleware.urlRedirects.adminSSLAndHostRedirect,
|
|
83
|
+
shared.middleware.prettyUrls,
|
|
84
84
|
notImplemented
|
|
85
85
|
];
|
|
@@ -142,19 +142,19 @@ module.exports = function apiRoutes() {
|
|
|
142
142
|
router.get('/session', mw.authAdminApi, http(api.session.read));
|
|
143
143
|
// We don't need auth when creating a new session (logging in)
|
|
144
144
|
router.post('/session',
|
|
145
|
-
shared.
|
|
146
|
-
shared.
|
|
145
|
+
shared.middleware.brute.globalBlock,
|
|
146
|
+
shared.middleware.brute.userLogin,
|
|
147
147
|
http(api.session.add)
|
|
148
148
|
);
|
|
149
149
|
router.del('/session', mw.authAdminApi, http(api.session.delete));
|
|
150
150
|
|
|
151
151
|
// ## Authentication
|
|
152
152
|
router.post('/authentication/passwordreset',
|
|
153
|
-
shared.
|
|
154
|
-
shared.
|
|
153
|
+
shared.middleware.brute.globalReset,
|
|
154
|
+
shared.middleware.brute.userReset,
|
|
155
155
|
http(api.authentication.generateResetToken)
|
|
156
156
|
);
|
|
157
|
-
router.put('/authentication/passwordreset', shared.
|
|
157
|
+
router.put('/authentication/passwordreset', shared.middleware.brute.globalBlock, http(api.authentication.resetPassword));
|
|
158
158
|
router.post('/authentication/invitation', http(api.authentication.acceptInvitation));
|
|
159
159
|
router.get('/authentication/invitation', http(api.authentication.isInvitation));
|
|
160
160
|
router.post('/authentication/setup', http(api.authentication.setup));
|
|
@@ -18,17 +18,17 @@ module.exports = function setupApiApp() {
|
|
|
18
18
|
apiApp.use(boolParser());
|
|
19
19
|
|
|
20
20
|
// send 503 json response in case of maintenance
|
|
21
|
-
apiApp.use(shared.
|
|
21
|
+
apiApp.use(shared.middleware.maintenance);
|
|
22
22
|
|
|
23
23
|
// API shouldn't be cached
|
|
24
|
-
apiApp.use(shared.
|
|
24
|
+
apiApp.use(shared.middleware.cacheControl('private'));
|
|
25
25
|
|
|
26
26
|
// Routing
|
|
27
27
|
apiApp.use(routes());
|
|
28
28
|
|
|
29
29
|
// API error handling
|
|
30
|
-
apiApp.use(shared.
|
|
31
|
-
apiApp.use(shared.
|
|
30
|
+
apiApp.use(shared.middleware.errorHandler.resourceNotFound);
|
|
31
|
+
apiApp.use(shared.middleware.errorHandler.handleJSONResponse);
|
|
32
32
|
|
|
33
33
|
debug('Content API v2 setup end');
|
|
34
34
|
|
|
@@ -14,10 +14,10 @@ const shared = require('../../../shared');
|
|
|
14
14
|
* Authentication for public endpoints
|
|
15
15
|
*/
|
|
16
16
|
module.exports.authenticatePublic = [
|
|
17
|
-
shared.
|
|
17
|
+
shared.middleware.brute.contentApiKey,
|
|
18
18
|
auth.authenticate.authenticateContentApi,
|
|
19
19
|
auth.authorize.authorizeContentApi,
|
|
20
20
|
cors(),
|
|
21
|
-
shared.
|
|
22
|
-
shared.
|
|
21
|
+
shared.middleware.urlRedirects.adminSSLAndHostRedirect,
|
|
22
|
+
shared.middleware.prettyUrls
|
|
23
23
|
];
|
|
@@ -20,21 +20,21 @@ module.exports = function setupApiApp() {
|
|
|
20
20
|
apiApp.use(boolParser());
|
|
21
21
|
|
|
22
22
|
// send 503 json response in case of maintenance
|
|
23
|
-
apiApp.use(shared.
|
|
23
|
+
apiApp.use(shared.middleware.maintenance);
|
|
24
24
|
|
|
25
25
|
// Check version matches for API requests, depends on res.locals.safeVersion being set
|
|
26
26
|
// Therefore must come after themeHandler.ghostLocals, for now
|
|
27
27
|
apiApp.use(apiMw.versionMatch);
|
|
28
28
|
|
|
29
29
|
// Admin API shouldn't be cached
|
|
30
|
-
apiApp.use(shared.
|
|
30
|
+
apiApp.use(shared.middleware.cacheControl('private'));
|
|
31
31
|
|
|
32
32
|
// Routing
|
|
33
33
|
apiApp.use(routes());
|
|
34
34
|
|
|
35
35
|
// API error handling
|
|
36
|
-
apiApp.use(shared.
|
|
37
|
-
apiApp.use(shared.
|
|
36
|
+
apiApp.use(shared.middleware.errorHandler.resourceNotFound);
|
|
37
|
+
apiApp.use(shared.middleware.errorHandler.handleJSONResponseV2);
|
|
38
38
|
|
|
39
39
|
debug('Admin API v3 setup end');
|
|
40
40
|
|