ghost 6.0.0-alpha.1 → 6.0.0-alpha.2

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.
Files changed (36) hide show
  1. package/components/tryghost-i18n-6.0.0-alpha.2.tgz +0 -0
  2. package/core/built/admin/assets/admin-x-activitypub/admin-x-activitypub.js +2 -2
  3. package/core/built/admin/assets/admin-x-activitypub/{index-rDFm98Ub.mjs → index-BZDwG-OG.mjs} +21488 -15398
  4. package/core/built/admin/assets/admin-x-activitypub/{index-BhgdXgH_.mjs → index-DTlSQCGz.mjs} +2 -2
  5. package/core/built/admin/assets/admin-x-settings/{CodeEditorView-CA2VVtOE.mjs → CodeEditorView-CCUvrZhe.mjs} +2 -2
  6. package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +3 -3
  7. package/core/built/admin/assets/admin-x-settings/{index-KA2tjCkS.mjs → index-Cubs_8W6.mjs} +8085 -8529
  8. package/core/built/admin/assets/admin-x-settings/{index-Dl3F40x5.mjs → index-D0ejKdD5.mjs} +2 -2
  9. package/core/built/admin/assets/admin-x-settings/{modals-B0zezufO.mjs → modals-DSxs9dLy.mjs} +8385 -8331
  10. package/core/built/admin/assets/{chunk.524.996c1c4d269fa6a50e90.js → chunk.524.0953dd72ae1efbabe0de.js} +6 -6
  11. package/core/built/admin/assets/{chunk.582.75cf44e5d1b925adf16d.js → chunk.582.3caa825c2a91efc48f1d.js} +9 -9
  12. package/core/built/admin/assets/{ghost-5a5b2112df68dfaf6813ce38cad16847.js → ghost-db0f84981913aec8a672c57aa22da07a.js} +58 -69
  13. package/core/built/admin/assets/posts/posts.js +21585 -21648
  14. package/core/built/admin/assets/stats/stats.js +21539 -21598
  15. package/core/built/admin/assets/{vendor-c89102f24c3d9502e9db741509767580.js → vendor-aed0068cf9b67d042dd23a6343545b7b.js} +1 -1
  16. package/core/built/admin/index.html +4 -4
  17. package/core/frontend/web/middleware/frontend-caching.js +6 -1
  18. package/core/frontend/web/middleware/static-theme.js +21 -4
  19. package/core/server/data/tinybird/ARCHITECTURE.md +0 -4
  20. package/core/server/data/tinybird/DOCS.md +0 -4
  21. package/core/server/services/explore-ping/ExplorePingService.js +3 -1
  22. package/core/server/services/members/members-api/controllers/RouterController.js +28 -10
  23. package/core/server/services/members/members-api/utils/normalize-email.js +31 -0
  24. package/core/server/services/public-config/config.js +0 -1
  25. package/core/server/services/settings/SettingsBREADService.js +5 -1
  26. package/core/server/services/settings/settings-service.js +3 -1
  27. package/core/server/services/settings-helpers/SettingsHelpers.js +0 -12
  28. package/core/server/web/admin/app.js +5 -6
  29. package/core/server/web/shared/middleware/cache-control.js +2 -1
  30. package/core/shared/config/defaults.json +6 -0
  31. package/core/shared/labs.js +0 -3
  32. package/package.json +10 -10
  33. package/tsconfig.tsbuildinfo +1 -1
  34. package/yarn.lock +140 -95
  35. package/components/tryghost-i18n-6.0.0-alpha.1.tgz +0 -0
  36. package/core/built/admin/assets/img/twitter-7a7a0ba12d9b5bfb8a2058764a827c31.svg +0 -4
@@ -9553,4 +9553,4 @@ e.default=class{constructor(e){if(this._data=new t.default,e)for(let t=0;t<e.len
9553
9553
  return this}get(e){let t=this._data[e]
9554
9554
  return t===r.UNDEFINED_KEY?void 0:t}set(e,t){return this._data[e]=t,this}delete(e){return this._data[e]=r.UNDEFINED_KEY,!0}}})
9555
9555
 
9556
- //# sourceMappingURL=vendor-1552b6fd843e2f460183ad9424be2a2d.map
9556
+ //# sourceMappingURL=vendor-326b46cbc2845d47f1e0af43ba21caec.map
@@ -6,7 +6,7 @@
6
6
  <title>Ghost</title>
7
7
 
8
8
 
9
- <meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22cdnUrl%22%3A%22%22%2C%22editorUrl%22%3A%22%22%2C%22rootURL%22%3A%22%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%226.0%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%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%2C%22editorFilename%22%3A%22koenig-lexical.umd.js%22%2C%22editorHash%22%3A%2237bd1e3e4d%22%2C%22adminXSettingsFilename%22%3A%22admin-x-settings.js%22%2C%22adminXSettingsHash%22%3A%222a69308990%22%2C%22adminXActivitypubFilename%22%3A%22admin-x-activitypub.js%22%2C%22adminXActivitypubHash%22%3A%226d96d71fe8%22%2C%22postsFilename%22%3A%22posts.js%22%2C%22postsHash%22%3A%222e1f1f92c6%22%2C%22statsFilename%22%3A%22stats.js%22%2C%22statsHash%22%3A%22e519dc949c%22%2C%22adminXActivitypubCustomUrl%22%3A%22https%3A%2F%2Fcdn.jsdelivr.net%2Fghost%2Fadmin-x-activitypub%400%2Fdist%2Fadmin-x-activitypub.js%22%7D" />
9
+ <meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22cdnUrl%22%3A%22%22%2C%22editorUrl%22%3A%22%22%2C%22rootURL%22%3A%22%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%226.0%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%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%2C%22editorFilename%22%3A%22koenig-lexical.umd.js%22%2C%22editorHash%22%3A%2237bd1e3e4d%22%2C%22adminXSettingsFilename%22%3A%22admin-x-settings.js%22%2C%22adminXSettingsHash%22%3A%22d033b6dcc1%22%2C%22adminXActivitypubFilename%22%3A%22admin-x-activitypub.js%22%2C%22adminXActivitypubHash%22%3A%2235ce5f3180%22%2C%22postsFilename%22%3A%22posts.js%22%2C%22postsHash%22%3A%22be6ec893bf%22%2C%22statsFilename%22%3A%22stats.js%22%2C%22statsHash%22%3A%225ddc4bf3f4%22%2C%22adminXActivitypubCustomUrl%22%3A%22https%3A%2F%2Fcdn.jsdelivr.net%2Fghost%2Fadmin-x-activitypub%400%2Fdist%2Fadmin-x-activitypub.js%22%7D" />
10
10
 
11
11
  <meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1, minimal-ui, viewport-fit=cover" />
12
12
  <meta name="pinterest" content="nopin" />
@@ -47,9 +47,9 @@
47
47
 
48
48
  <div id="ember-basic-dropdown-wormhole"></div>
49
49
 
50
- <script src="assets/vendor-c89102f24c3d9502e9db741509767580.js"></script>
50
+ <script src="assets/vendor-aed0068cf9b67d042dd23a6343545b7b.js"></script>
51
51
  <script src="assets/chunk.728.985c45ad584b4b91ca60.js"></script>
52
- <script src="assets/chunk.524.996c1c4d269fa6a50e90.js"></script>
53
- <script src="assets/ghost-5a5b2112df68dfaf6813ce38cad16847.js"></script>
52
+ <script src="assets/chunk.524.0953dd72ae1efbabe0de.js"></script>
53
+ <script src="assets/ghost-db0f84981913aec8a672c57aa22da07a.js"></script>
54
54
  </body>
55
55
  </html>
@@ -51,6 +51,11 @@ const getMiddleware = async (getFreeTier = async () => {
51
51
  return shared.middleware.cacheControl('private')(req, res, next);
52
52
  }
53
53
 
54
+ // CASE: Never cache preview routes
55
+ if (req.path?.startsWith('/p/')) {
56
+ return shared.middleware.cacheControl('noCache')(req, res, next);
57
+ }
58
+
54
59
  // CASE: Cache member's content if this feature is enabled
55
60
  if (req.member && shouldCacheMembersContent) {
56
61
  // Set the 'cache-control' header to 'public'
@@ -74,4 +79,4 @@ const getMiddleware = async (getFreeTier = async () => {
74
79
  module.exports = {
75
80
  getMiddleware,
76
81
  calculateMemberTier // exported for testing
77
- };
82
+ };
@@ -59,18 +59,35 @@ function forwardToExpressStatic(req, res, next) {
59
59
  return next();
60
60
  }
61
61
 
62
- const configMaxAge = config.get('caching:theme:maxAge');
62
+ // We allow robots.txt to fall through to the next middleware, so that we can return our default robots.txt
63
+ // We also allow sitemap.xml and sitemap-:resource.xml to fall through so that we can serve our defaults if they're not found in the theme
64
+ const fallthroughFiles = [
65
+ '/robots.txt',
66
+ '/sitemap.xml',
67
+ '/sitemap-posts.xml',
68
+ '/sitemap-pages.xml',
69
+ '/sitemap-tags.xml',
70
+ '/sitemap-authors.xml',
71
+ '/sitemap-users.xml',
72
+ '/sitemap.xsl'
73
+ ];
74
+ const fallthrough = fallthroughFiles.includes(req.path) ? true : false;
63
75
 
64
- // @NOTE: the maxAge config passed below are in milliseconds and the config
65
- // is specified in seconds. See https://github.com/expressjs/serve-static/issues/150 for more context
66
76
  express.static(themeEngine.getActive().path, {
67
- maxAge: (configMaxAge || configMaxAge === 0) ? configMaxAge : (365 * 24 * 60 * 60 * 1000) // Default to 1 year in ms
77
+ // @NOTE: the maxAge config passed below are in milliseconds and the config
78
+ // is specified in seconds. See https://github.com/expressjs/serve-static/issues/150 for more context
79
+ maxAge: config.get('caching:theme:maxAge') * 1000,
80
+ fallthrough
68
81
  }
69
82
  )(req, res, next);
70
83
  }
71
84
 
72
85
  function staticTheme() {
73
86
  return function denyStatic(req, res, next) {
87
+ if (!path.extname(req.path)) {
88
+ return next();
89
+ }
90
+
74
91
  if (!isAllowedFile(req.path.toLowerCase()) && isDeniedFile(req.path.toLowerCase())) {
75
92
  return next();
76
93
  }
@@ -303,10 +303,6 @@ Click Tracking → redirects → members_click_events
303
303
  2. MySQL tracks member signup with attribution
304
304
  3. MySQL tracks paid conversion with attribution
305
305
 
306
- ## API Endpoints
307
-
308
- All endpoints require authentication (`mw.authAdminApi`) and are gated behind `labs.isSet('trafficAnalytics')`.
309
-
310
306
  ### Core Stats Endpoints
311
307
 
312
308
  ```javascript
@@ -311,10 +311,6 @@ Click Tracking → redirects → members_click_events
311
311
  3. MySQL tracks paid conversion with attribution
312
312
  4. Combined view shows complete funnel
313
313
 
314
- ## API Endpoints
315
-
316
- All endpoints require authentication (`mw.authAdminApi`) and are gated behind `labs.isSet('trafficAnalytics')`.
317
-
318
314
  ### Core Stats Endpoints
319
315
 
320
316
  ```javascript
@@ -33,7 +33,9 @@ module.exports = class ExplorePingService {
33
33
  ghost: this.ghostVersion.full,
34
34
  site_uuid: this.settingsCache.get('site_uuid'),
35
35
  url: this.config.get('url'),
36
- theme: this.settingsCache.get('active_theme')
36
+ theme: this.settingsCache.get('active_theme'),
37
+ facebook: this.settingsCache.get('facebook'),
38
+ twitter: this.settingsCache.get('twitter')
37
39
  };
38
40
 
39
41
  try {
@@ -4,6 +4,7 @@ const sanitizeHtml = require('sanitize-html');
4
4
  const {BadRequestError, NoPermissionError, UnauthorizedError, DisabledFeatureError, NotFoundError} = require('@tryghost/errors');
5
5
  const errors = require('@tryghost/errors');
6
6
  const {isEmail} = require('@tryghost/validator');
7
+ const normalizeEmail = require('../utils/normalize-email');
7
8
 
8
9
  const messages = {
9
10
  emailRequired: 'Email is required.',
@@ -585,6 +586,23 @@ module.exports = class RouterController {
585
586
  });
586
587
  }
587
588
 
589
+ // Normalize email to prevent homograph attacks
590
+ let normalizedEmail;
591
+
592
+ try {
593
+ normalizedEmail = normalizeEmail(email);
594
+
595
+ if (normalizedEmail !== email) {
596
+ logging.info(`Email normalized from ${email} to ${normalizedEmail} for magic link`);
597
+ }
598
+ } catch (err) {
599
+ logging.error(`Failed to normalize [${email}]: ${err.message}`);
600
+
601
+ throw new errors.BadRequestError({
602
+ message: tpl(messages.invalidEmail)
603
+ });
604
+ }
605
+
588
606
  if (honeypot) {
589
607
  logging.warn('Honeypot field filled, this is likely a bot');
590
608
 
@@ -606,9 +624,9 @@ module.exports = class RouterController {
606
624
 
607
625
  try {
608
626
  if (emailType === 'signup' || emailType === 'subscribe') {
609
- await this._handleSignup(req, referrer);
627
+ await this._handleSignup(req, normalizedEmail, referrer);
610
628
  } else {
611
- await this._handleSignin(req, referrer);
629
+ await this._handleSignin(req, normalizedEmail, referrer);
612
630
  }
613
631
 
614
632
  res.writeHead(201);
@@ -626,7 +644,7 @@ module.exports = class RouterController {
626
644
  }
627
645
  }
628
646
 
629
- async _handleSignup(req, referrer = null) {
647
+ async _handleSignup(req, normalizedEmail, referrer = null) {
630
648
  if (!this._allowSelfSignup()) {
631
649
  if (this._settingsCache.get('members_signup_access') === 'paid') {
632
650
  throw new errors.BadRequestError({
@@ -640,14 +658,14 @@ module.exports = class RouterController {
640
658
  }
641
659
 
642
660
  const blockedEmailDomains = this._settingsCache.get('all_blocked_email_domains');
643
- const emailDomain = req.body.email.split('@')[1]?.toLowerCase();
661
+ const emailDomain = normalizedEmail.split('@')[1]?.toLowerCase();
644
662
  if (emailDomain && blockedEmailDomains.includes(emailDomain)) {
645
663
  throw new errors.BadRequestError({
646
664
  message: tpl(messages.blockedEmailDomain)
647
665
  });
648
666
  }
649
667
 
650
- const {email, emailType} = req.body;
668
+ const {emailType} = req.body;
651
669
 
652
670
  const tokenData = {
653
671
  labels: req.body.labels,
@@ -657,13 +675,13 @@ module.exports = class RouterController {
657
675
  attribution: await this._memberAttributionService.getAttribution(req.body.urlHistory)
658
676
  };
659
677
 
660
- return await this._sendEmailWithMagicLink({email, tokenData, requestedType: emailType, referrer});
678
+ return await this._sendEmailWithMagicLink({email: normalizedEmail, tokenData, requestedType: emailType, referrer});
661
679
  }
662
680
 
663
- async _handleSignin(req, referrer = null) {
664
- const {email, emailType} = req.body;
681
+ async _handleSignin(req, normalizedEmail, referrer = null) {
682
+ const {emailType} = req.body;
665
683
 
666
- const member = await this._memberRepository.get({email});
684
+ const member = await this._memberRepository.get({email: normalizedEmail});
667
685
 
668
686
  if (!member) {
669
687
  throw new errors.BadRequestError({
@@ -672,7 +690,7 @@ module.exports = class RouterController {
672
690
  }
673
691
 
674
692
  const tokenData = {};
675
- return await this._sendEmailWithMagicLink({email, tokenData, requestedType: emailType, referrer});
693
+ return await this._sendEmailWithMagicLink({email: normalizedEmail, tokenData, requestedType: emailType, referrer});
676
694
  }
677
695
 
678
696
  /**
@@ -0,0 +1,31 @@
1
+ const punycode = require('punycode/');
2
+
3
+ /**
4
+ * Normalizes email addresses by converting Unicode domains to ASCII (punycode)
5
+ * This prevents homograph attacks where Unicode characters are used to spoof
6
+ * domains
7
+ *
8
+ * @param {string} email The email address to normalize
9
+ * @returns {string} The normalized email address
10
+ * @throws {Error} When punycode conversion fails
11
+ */
12
+ function normalizeEmail(email) {
13
+ if (!email || typeof email !== 'string') {
14
+ return null;
15
+ }
16
+
17
+ const atIndex = email.lastIndexOf('@');
18
+
19
+ if (atIndex === -1) {
20
+ return email;
21
+ }
22
+
23
+ const localPart = email.substring(0, atIndex);
24
+ const domainPart = email.substring(atIndex + 1);
25
+
26
+ const asciiDomain = punycode.toASCII(domainPart);
27
+
28
+ return `${localPart}@${asciiDomain}`;
29
+ }
30
+
31
+ module.exports = normalizeEmail;
@@ -29,7 +29,6 @@ module.exports = function getConfigProperties() {
29
29
  configProperties.exploreTestimonialsUrl = config.get('explore:testimonials_url');
30
30
  }
31
31
 
32
- // WIP tinybird stats feature - it's entirely config driven instead of using an alpha flag for now
33
32
  if (config.get('tinybird') && config.get('tinybird:stats')) {
34
33
  const statsConfig = config.get('tinybird:stats');
35
34
  const siteUuid = statsConfig.id || settingsCache.get('site_uuid');
@@ -24,12 +24,14 @@ class SettingsBREADService {
24
24
  * @param {Object} options.singleUseTokenProvider
25
25
  * @param {Object} options.urlUtils
26
26
  * @param {Object} options.labsService - labs service instance
27
+ * @param {Object} options.limitsService - limits service instance
27
28
  * @param {{service: Object}} options.emailAddressService
28
29
  */
29
- constructor({SettingsModel, settingsCache, labsService, mail, singleUseTokenProvider, urlUtils, emailAddressService}) {
30
+ constructor({SettingsModel, settingsCache, labsService, limitsService, mail, singleUseTokenProvider, urlUtils, emailAddressService}) {
30
31
  this.SettingsModel = SettingsModel;
31
32
  this.settingsCache = settingsCache;
32
33
  this.labs = labsService;
34
+ this.limitsService = limitsService;
33
35
  this.emailAddressService = emailAddressService;
34
36
 
35
37
  /* email verification setup */
@@ -194,6 +196,8 @@ class SettingsBREADService {
194
196
  }
195
197
 
196
198
  if (stripeConnectData) {
199
+ await this.limitsService.errorIfWouldGoOverLimit('limitStripeConnect');
200
+
197
201
  filteredSettings.push({
198
202
  key: 'stripe_connect_publishable_key',
199
203
  value: stripeConnectData.public_key
@@ -5,6 +5,7 @@
5
5
  const events = require('../../lib/common/events');
6
6
  const models = require('../../models');
7
7
  const labs = require('../../../shared/labs');
8
+ const limits = require('../limits');
8
9
  const config = require('../../../shared/config');
9
10
  const adapterManager = require('../adapter-manager');
10
11
  const SettingsCache = require('../../../shared/settings-cache');
@@ -30,6 +31,7 @@ const getSettingsBREADServiceInstance = () => {
30
31
  SettingsModel: models.Settings,
31
32
  settingsCache: SettingsCache,
32
33
  labsService: labs,
34
+ limitsService: limits,
33
35
  mail,
34
36
  singleUseTokenProvider: new SingleUseTokenProvider({
35
37
  SingleUseTokenModel: models.SingleUseToken,
@@ -110,7 +112,7 @@ module.exports = {
110
112
  fields.push(new CalculatedField({key: 'social_web_enabled', type: 'boolean', group: 'social_web', fn: settingsHelpers.isSocialWebEnabled.bind(settingsHelpers), dependents: ['social_web', 'labs']}));
111
113
 
112
114
  // Web analytics
113
- fields.push(new CalculatedField({key: 'web_analytics_enabled', type: 'boolean', group: 'analytics', fn: settingsHelpers.isWebAnalyticsEnabled.bind(settingsHelpers), dependents: ['web_analytics', 'labs']}));
115
+ fields.push(new CalculatedField({key: 'web_analytics_enabled', type: 'boolean', group: 'analytics', fn: settingsHelpers.isWebAnalyticsEnabled.bind(settingsHelpers), dependents: ['web_analytics']}));
114
116
  fields.push(new CalculatedField({key: 'web_analytics_configured', type: 'boolean', group: 'analytics', fn: settingsHelpers.isWebAnalyticsConfigured.bind(settingsHelpers), dependents: ['web_analytics']}));
115
117
 
116
118
  return fields;
@@ -234,12 +234,6 @@ class SettingsHelpers {
234
234
  return false;
235
235
  }
236
236
 
237
- // Labs setting
238
- if (!this.labs.isSet('ActivityPub')) {
239
- debug('Social web is disabled in labs');
240
- return false;
241
- }
242
-
243
237
  // Ghost (Pro) limits
244
238
  if (this.limitService.isDisabled('limitSocialWeb')) {
245
239
  debug('Social web is not available for Ghost (Pro) sites without a custom domain, or hosted on a subdirectory');
@@ -279,12 +273,6 @@ class SettingsHelpers {
279
273
  return false;
280
274
  }
281
275
 
282
- // Labs setting
283
- if (!this.labs.isSet('trafficAnalytics')) {
284
- debug('Web analytics is disabled in labs');
285
- return false;
286
- }
287
-
288
276
  // Check if web analytics can be configured (limit service and required config)
289
277
  if (!this.isWebAnalyticsConfigured()) {
290
278
  return false;
@@ -19,16 +19,15 @@ module.exports = function setupAdminApp() {
19
19
  const adminApp = express('admin');
20
20
 
21
21
  // Admin assets
22
- // @TODO ensure this gets a local 404 error handler
23
- const configMaxAge = config.get('caching:admin:maxAge');
24
22
  // @NOTE: when we start working on HTTP/3 optimizations the immutable headers
25
23
  // produced below should be split into separate 'Cache-Control' entry.
26
24
  // For reference see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#validation_2
27
- // @NOTE: the maxAge config passed below are in milliseconds and the config
28
- // is specified in seconds. See https://github.com/expressjs/serve-static/issues/150 for more context
25
+
29
26
  adminApp.use('/assets', serveStatic(
30
27
  path.join(config.get('paths').adminAssets, 'assets'), {
31
- maxAge: (configMaxAge || configMaxAge === 0) ? configMaxAge : (365 * 24 * 60 * 60 * 1000), // Default to 1 year in ms
28
+ // @NOTE: the maxAge config passed below are in milliseconds and the config
29
+ // is specified in seconds. See https://github.com/expressjs/serve-static/issues/150 for more context
30
+ maxAge: config.get('caching:admin:maxAge') * 1000,
32
31
  immutable: true,
33
32
  fallthrough: false
34
33
  }
@@ -94,4 +93,4 @@ module.exports = function setupAdminApp() {
94
93
  debug('Admin setup end');
95
94
 
96
95
  return adminApp;
97
- };
96
+ };
@@ -9,7 +9,7 @@
9
9
  const isString = require('lodash/isString');
10
10
 
11
11
  /**
12
- * @param {'public'|'private'} profile Use "private" if you do not want caching
12
+ * @param {'public'|'private'|'noCache'} profile Use "private" if you do not want caching
13
13
  * @param {object} [options]
14
14
  * @param {number} [options.maxAge] The max-age in seconds to use when profile is "public"
15
15
  * @param {number} [options.staleWhileRevalidate] The stale-while-revalidate in seconds to use when profile is "public"
@@ -24,6 +24,7 @@ const cacheControl = (profile, options = {maxAge: 0}) => {
24
24
 
25
25
  const profiles = {
26
26
  public: publicOptions.filter(option => option).join(', '),
27
+ noCache: 'no-cache, max-age=0, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0',
27
28
  private: 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0'
28
29
  };
29
30
 
@@ -168,6 +168,12 @@
168
168
  },
169
169
  "commentsCountAPI": {
170
170
  "maxAge": 0
171
+ },
172
+ "admin": {
173
+ "maxAge": 31536000
174
+ },
175
+ "theme": {
176
+ "maxAge": 31536000
171
177
  }
172
178
  },
173
179
  "optimization": {
@@ -27,9 +27,6 @@ const GA_FEATURES = [
27
27
  'announcementBar',
28
28
  'customFonts',
29
29
  'contentVisibility',
30
- 'ActivityPub',
31
- 'trafficAnalytics',
32
- 'ui60',
33
30
  'explore'
34
31
  ];
35
32
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ghost",
3
- "version": "6.0.0-alpha.1",
3
+ "version": "6.0.0-alpha.2",
4
4
  "description": "The professional publishing platform",
5
5
  "author": "Ghost Foundation",
6
6
  "homepage": "https://ghost.org",
@@ -86,7 +86,7 @@
86
86
  "@tryghost/helpers": "1.1.97",
87
87
  "@tryghost/html-to-plaintext": "1.0.4",
88
88
  "@tryghost/http-cache-utils": "0.1.20",
89
- "@tryghost/i18n": "file:components/tryghost-i18n-6.0.0-alpha.1.tgz",
89
+ "@tryghost/i18n": "file:components/tryghost-i18n-6.0.0-alpha.2.tgz",
90
90
  "@tryghost/image-transform": "1.4.6",
91
91
  "@tryghost/job-manager": "1.0.3",
92
92
  "@tryghost/kg-card-factory": "5.1.2",
@@ -135,9 +135,9 @@
135
135
  "clsx": "2.1.1",
136
136
  "cluster-key-slot": "1.1.2",
137
137
  "common-tags": "1.8.2",
138
- "compression": "1.8.0",
138
+ "compression": "1.8.1",
139
139
  "connect-slashes": "1.4.0",
140
- "cookie-session": "2.1.0",
140
+ "cookie-session": "2.1.1",
141
141
  "cookies": "0.9.1",
142
142
  "cors": "2.8.5",
143
143
  "countries-and-timezones": "3.8.0",
@@ -154,9 +154,9 @@
154
154
  "express-lazy-router": "1.0.6",
155
155
  "express-query-boolean": "2.0.0",
156
156
  "express-queue": "0.0.13",
157
- "express-session": "1.18.1",
157
+ "express-session": "1.18.2",
158
158
  "file-type": "16.5.4",
159
- "form-data": "4.0.3",
159
+ "form-data": "4.0.4",
160
160
  "fs-extra": "11.3.0",
161
161
  "ghost-storage-base": "1.0.0",
162
162
  "glob": "8.1.0",
@@ -195,7 +195,7 @@
195
195
  "mime-types": "2.1.35",
196
196
  "moment": "2.24.0",
197
197
  "moment-timezone": "0.5.45",
198
- "multer": "2.0.1",
198
+ "multer": "2.0.2",
199
199
  "mysql2": "3.14.2",
200
200
  "nconf": "0.13.0",
201
201
  "node-fetch": "2.7.0",
@@ -232,7 +232,7 @@
232
232
  "@types/bookshelf": "1.2.9",
233
233
  "@types/common-tags": "1.8.4",
234
234
  "@types/jsonwebtoken": "9.0.10",
235
- "@types/node": "22.16.4",
235
+ "@types/node": "22.16.5",
236
236
  "@types/node-jose": "1.1.13",
237
237
  "@types/nodemailer": "6.4.17",
238
238
  "@types/sinon": "17.0.4",
@@ -244,7 +244,7 @@
244
244
  "detect-newline": "3.1.0",
245
245
  "expect": "29.7.0",
246
246
  "find-root": "1.1.0",
247
- "form-data": "4.0.3",
247
+ "form-data": "4.0.4",
248
248
  "html-minifier": "4.0.0",
249
249
  "html-validate": "8.29.0",
250
250
  "inquirer": "8.2.6",
@@ -273,7 +273,7 @@
273
273
  "jackspeak": "2.3.6",
274
274
  "moment": "2.24.0",
275
275
  "moment-timezone": "0.5.45",
276
- "@tryghost/i18n": "file:components/tryghost-i18n-6.0.0-alpha.1.tgz"
276
+ "@tryghost/i18n": "file:components/tryghost-i18n-6.0.0-alpha.2.tgz"
277
277
  },
278
278
  "nx": {
279
279
  "targets": {