ghost 5.74.3 → 5.74.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.
Files changed (161) hide show
  1. package/components/{tryghost-adapter-cache-memory-ttl-5.74.3.tgz → tryghost-adapter-cache-memory-ttl-5.74.4.tgz} +0 -0
  2. package/components/{tryghost-adapter-cache-redis-5.74.3.tgz → tryghost-adapter-cache-redis-5.74.4.tgz} +0 -0
  3. package/components/{tryghost-adapter-manager-5.74.3.tgz → tryghost-adapter-manager-5.74.4.tgz} +0 -0
  4. package/components/{tryghost-announcement-bar-settings-5.74.3.tgz → tryghost-announcement-bar-settings-5.74.4.tgz} +0 -0
  5. package/components/{tryghost-api-framework-5.74.3.tgz → tryghost-api-framework-5.74.4.tgz} +0 -0
  6. package/components/{tryghost-api-version-compatibility-service-5.74.3.tgz → tryghost-api-version-compatibility-service-5.74.4.tgz} +0 -0
  7. package/components/{tryghost-audience-feedback-5.74.3.tgz → tryghost-audience-feedback-5.74.4.tgz} +0 -0
  8. package/components/tryghost-bookshelf-repository-5.74.4.tgz +0 -0
  9. package/components/tryghost-bootstrap-socket-5.74.4.tgz +0 -0
  10. package/components/tryghost-collections-5.74.4.tgz +0 -0
  11. package/components/{tryghost-constants-5.74.3.tgz → tryghost-constants-5.74.4.tgz} +0 -0
  12. package/components/tryghost-custom-theme-settings-service-5.74.4.tgz +0 -0
  13. package/components/{tryghost-data-generator-5.74.3.tgz → tryghost-data-generator-5.74.4.tgz} +0 -0
  14. package/components/tryghost-domain-events-5.74.4.tgz +0 -0
  15. package/components/tryghost-donations-5.74.4.tgz +0 -0
  16. package/components/tryghost-dynamic-routing-events-5.74.4.tgz +0 -0
  17. package/components/tryghost-email-addresses-5.74.4.tgz +0 -0
  18. package/components/{tryghost-email-analytics-provider-mailgun-5.74.3.tgz → tryghost-email-analytics-provider-mailgun-5.74.4.tgz} +0 -0
  19. package/components/tryghost-email-analytics-service-5.74.4.tgz +0 -0
  20. package/components/{tryghost-email-content-generator-5.74.3.tgz → tryghost-email-content-generator-5.74.4.tgz} +0 -0
  21. package/components/{tryghost-email-events-5.74.3.tgz → tryghost-email-events-5.74.4.tgz} +0 -0
  22. package/components/tryghost-email-service-5.74.4.tgz +0 -0
  23. package/components/tryghost-email-suppression-list-5.74.4.tgz +0 -0
  24. package/components/{tryghost-event-aware-cache-wrapper-5.74.3.tgz → tryghost-event-aware-cache-wrapper-5.74.4.tgz} +0 -0
  25. package/components/{tryghost-express-dynamic-redirects-5.74.3.tgz → tryghost-express-dynamic-redirects-5.74.4.tgz} +0 -0
  26. package/components/tryghost-external-media-inliner-5.74.4.tgz +0 -0
  27. package/components/tryghost-extract-api-key-5.74.4.tgz +0 -0
  28. package/components/{tryghost-html-to-plaintext-5.74.3.tgz → tryghost-html-to-plaintext-5.74.4.tgz} +0 -0
  29. package/components/tryghost-i18n-5.74.4.tgz +0 -0
  30. package/components/tryghost-importer-handler-content-files-5.74.4.tgz +0 -0
  31. package/components/{tryghost-importer-revue-5.74.3.tgz → tryghost-importer-revue-5.74.4.tgz} +0 -0
  32. package/components/tryghost-in-memory-repository-5.74.4.tgz +0 -0
  33. package/components/{tryghost-job-manager-5.74.3.tgz → tryghost-job-manager-5.74.4.tgz} +0 -0
  34. package/components/{tryghost-link-redirects-5.74.3.tgz → tryghost-link-redirects-5.74.4.tgz} +0 -0
  35. package/components/{tryghost-link-replacer-5.74.3.tgz → tryghost-link-replacer-5.74.4.tgz} +0 -0
  36. package/components/{tryghost-link-tracking-5.74.3.tgz → tryghost-link-tracking-5.74.4.tgz} +0 -0
  37. package/components/{tryghost-magic-link-5.74.3.tgz → tryghost-magic-link-5.74.4.tgz} +0 -0
  38. package/components/tryghost-mail-events-5.74.4.tgz +0 -0
  39. package/components/tryghost-mailgun-client-5.74.4.tgz +0 -0
  40. package/components/tryghost-member-attribution-5.74.4.tgz +0 -0
  41. package/components/{tryghost-member-events-5.74.3.tgz → tryghost-member-events-5.74.4.tgz} +0 -0
  42. package/components/{tryghost-members-api-5.74.3.tgz → tryghost-members-api-5.74.4.tgz} +0 -0
  43. package/components/tryghost-members-csv-5.74.4.tgz +0 -0
  44. package/components/tryghost-members-events-service-5.74.4.tgz +0 -0
  45. package/components/{tryghost-members-importer-5.74.3.tgz → tryghost-members-importer-5.74.4.tgz} +0 -0
  46. package/components/tryghost-members-offers-5.74.4.tgz +0 -0
  47. package/components/tryghost-members-payments-5.74.4.tgz +0 -0
  48. package/components/tryghost-members-ssr-5.74.4.tgz +0 -0
  49. package/components/{tryghost-members-stripe-service-5.74.3.tgz → tryghost-members-stripe-service-5.74.4.tgz} +0 -0
  50. package/components/{tryghost-mentions-email-report-5.74.3.tgz → tryghost-mentions-email-report-5.74.4.tgz} +0 -0
  51. package/components/tryghost-milestones-5.74.4.tgz +0 -0
  52. package/components/tryghost-minifier-5.74.4.tgz +0 -0
  53. package/components/tryghost-model-to-domain-event-interceptor-5.74.4.tgz +0 -0
  54. package/components/{tryghost-mw-api-version-mismatch-5.74.3.tgz → tryghost-mw-api-version-mismatch-5.74.4.tgz} +0 -0
  55. package/components/tryghost-mw-cache-control-5.74.4.tgz +0 -0
  56. package/components/{tryghost-mw-error-handler-5.74.3.tgz → tryghost-mw-error-handler-5.74.4.tgz} +0 -0
  57. package/components/tryghost-mw-session-from-token-5.74.4.tgz +0 -0
  58. package/components/{tryghost-mw-update-user-last-seen-5.74.3.tgz → tryghost-mw-update-user-last-seen-5.74.4.tgz} +0 -0
  59. package/components/tryghost-mw-version-match-5.74.4.tgz +0 -0
  60. package/components/tryghost-mw-vhost-5.74.4.tgz +0 -0
  61. package/components/tryghost-nql-filter-expansions-5.74.4.tgz +0 -0
  62. package/components/tryghost-oembed-service-5.74.4.tgz +0 -0
  63. package/components/tryghost-package-json-5.74.4.tgz +0 -0
  64. package/components/{tryghost-post-events-5.74.3.tgz → tryghost-post-events-5.74.4.tgz} +0 -0
  65. package/components/tryghost-post-revisions-5.74.4.tgz +0 -0
  66. package/components/{tryghost-posts-service-5.74.3.tgz → tryghost-posts-service-5.74.4.tgz} +0 -0
  67. package/components/tryghost-recommendations-5.74.4.tgz +0 -0
  68. package/components/{tryghost-referrers-5.74.3.tgz → tryghost-referrers-5.74.4.tgz} +0 -0
  69. package/components/{tryghost-security-5.74.3.tgz → tryghost-security-5.74.4.tgz} +0 -0
  70. package/components/{tryghost-session-service-5.74.3.tgz → tryghost-session-service-5.74.4.tgz} +0 -0
  71. package/components/tryghost-settings-path-manager-5.74.4.tgz +0 -0
  72. package/components/{tryghost-slack-notifications-5.74.3.tgz → tryghost-slack-notifications-5.74.4.tgz} +0 -0
  73. package/components/tryghost-staff-service-5.74.4.tgz +0 -0
  74. package/components/tryghost-stats-service-5.74.4.tgz +0 -0
  75. package/components/{tryghost-tiers-5.74.3.tgz → tryghost-tiers-5.74.4.tgz} +0 -0
  76. package/components/{tryghost-update-check-service-5.74.3.tgz → tryghost-update-check-service-5.74.4.tgz} +0 -0
  77. package/components/tryghost-verification-trigger-5.74.4.tgz +0 -0
  78. package/components/tryghost-version-notifications-data-service-5.74.4.tgz +0 -0
  79. package/components/tryghost-webmentions-5.74.4.tgz +0 -0
  80. package/core/boot.js +4 -0
  81. package/core/built/admin/assets/admin-x-demo/admin-x-demo.js +6 -0
  82. package/core/built/admin/assets/admin-x-demo/index-2fa5a3c2.mjs +8291 -0
  83. package/core/built/admin/assets/admin-x-demo/modals-10ef986a.mjs +381 -0
  84. package/core/built/admin/assets/admin-x-settings/{CodeEditorView-f1193dcf.mjs → CodeEditorView-b41ea5f8.mjs} +76 -76
  85. package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +2 -2
  86. package/core/built/admin/assets/admin-x-settings/{index-114d5aab.mjs → index-bc03188e.mjs} +2 -2
  87. package/core/built/admin/assets/admin-x-settings/{index-89bf7b3f.mjs → index-e5bc288c.mjs} +8140 -7624
  88. package/core/built/admin/assets/admin-x-settings/{modals-045fb940.mjs → modals-98e015fa.mjs} +8005 -8080
  89. package/core/built/admin/assets/{chunk.761.5cfbe0d0f8ec7aefe7bc.js → chunk.137.0297fba124bc9ba1bbc1.js} +691 -686
  90. package/core/built/admin/assets/{chunk.143.52a7a015be62e953ba15.js → chunk.143.0c31227118654150d49c.js} +5 -5
  91. package/core/built/admin/assets/{chunk.178.d740bad71c6bc15679c9.js → chunk.178.b2467b9ba07dbbc9016a.js} +4 -4
  92. package/core/built/admin/assets/ghost-0456ea5c74bd1b27239c11706d1acae9.css +1 -0
  93. package/core/built/admin/assets/{ghost-ad9952b86c1d886c49a92339adf702e3.js → ghost-7d0f91cd17901ac15ce0ae64a3091e2a.js} +225 -194
  94. package/core/built/admin/assets/ghost-dark-29863e517e1917a3b0df74979a6cf03f.css +1 -0
  95. package/core/built/admin/index.html +5 -5
  96. package/core/frontend/helpers/img_url.js +7 -97
  97. package/core/frontend/meta/image-dimensions.js +34 -6
  98. package/core/frontend/utils/images.js +100 -0
  99. package/core/server/api/endpoints/users.js +34 -5
  100. package/core/server/data/schema/schema.js +1 -1
  101. package/core/server/models/base/plugins/events.js +20 -1
  102. package/core/server/services/email-address/EmailAddressServiceWrapper.js +39 -0
  103. package/core/server/services/email-address/index.js +3 -0
  104. package/core/server/services/email-service/EmailServiceWrapper.js +2 -0
  105. package/core/server/services/mail/GhostMailer.js +46 -9
  106. package/core/server/services/members/service.js +8 -1
  107. package/core/server/services/newsletters/NewslettersService.js +52 -19
  108. package/core/server/services/newsletters/index.js +3 -1
  109. package/core/server/services/offers/OfferBookshelfRepository.js +8 -1
  110. package/core/server/services/settings/settings-service.js +4 -0
  111. package/core/server/services/settings-helpers/SettingsHelpers.js +71 -2
  112. package/core/server/services/settings-helpers/index.js +2 -1
  113. package/core/shared/config/overrides.json +2 -1
  114. package/core/shared/labs.js +5 -1
  115. package/core/shared/sentry.js +56 -33
  116. package/package.json +150 -149
  117. package/yarn.lock +696 -786
  118. package/components/tryghost-bookshelf-repository-5.74.3.tgz +0 -0
  119. package/components/tryghost-bootstrap-socket-5.74.3.tgz +0 -0
  120. package/components/tryghost-collections-5.74.3.tgz +0 -0
  121. package/components/tryghost-custom-theme-settings-service-5.74.3.tgz +0 -0
  122. package/components/tryghost-domain-events-5.74.3.tgz +0 -0
  123. package/components/tryghost-donations-5.74.3.tgz +0 -0
  124. package/components/tryghost-dynamic-routing-events-5.74.3.tgz +0 -0
  125. package/components/tryghost-email-analytics-service-5.74.3.tgz +0 -0
  126. package/components/tryghost-email-service-5.74.3.tgz +0 -0
  127. package/components/tryghost-email-suppression-list-5.74.3.tgz +0 -0
  128. package/components/tryghost-external-media-inliner-5.74.3.tgz +0 -0
  129. package/components/tryghost-extract-api-key-5.74.3.tgz +0 -0
  130. package/components/tryghost-i18n-5.74.3.tgz +0 -0
  131. package/components/tryghost-importer-handler-content-files-5.74.3.tgz +0 -0
  132. package/components/tryghost-in-memory-repository-5.74.3.tgz +0 -0
  133. package/components/tryghost-mail-events-5.74.3.tgz +0 -0
  134. package/components/tryghost-mailgun-client-5.74.3.tgz +0 -0
  135. package/components/tryghost-member-attribution-5.74.3.tgz +0 -0
  136. package/components/tryghost-members-csv-5.74.3.tgz +0 -0
  137. package/components/tryghost-members-events-service-5.74.3.tgz +0 -0
  138. package/components/tryghost-members-offers-5.74.3.tgz +0 -0
  139. package/components/tryghost-members-payments-5.74.3.tgz +0 -0
  140. package/components/tryghost-members-ssr-5.74.3.tgz +0 -0
  141. package/components/tryghost-milestones-5.74.3.tgz +0 -0
  142. package/components/tryghost-minifier-5.74.3.tgz +0 -0
  143. package/components/tryghost-model-to-domain-event-interceptor-5.74.3.tgz +0 -0
  144. package/components/tryghost-mw-cache-control-5.74.3.tgz +0 -0
  145. package/components/tryghost-mw-session-from-token-5.74.3.tgz +0 -0
  146. package/components/tryghost-mw-version-match-5.74.3.tgz +0 -0
  147. package/components/tryghost-mw-vhost-5.74.3.tgz +0 -0
  148. package/components/tryghost-nql-filter-expansions-5.74.3.tgz +0 -0
  149. package/components/tryghost-oembed-service-5.74.3.tgz +0 -0
  150. package/components/tryghost-package-json-5.74.3.tgz +0 -0
  151. package/components/tryghost-post-revisions-5.74.3.tgz +0 -0
  152. package/components/tryghost-recommendations-5.74.3.tgz +0 -0
  153. package/components/tryghost-settings-path-manager-5.74.3.tgz +0 -0
  154. package/components/tryghost-staff-service-5.74.3.tgz +0 -0
  155. package/components/tryghost-stats-service-5.74.3.tgz +0 -0
  156. package/components/tryghost-verification-trigger-5.74.3.tgz +0 -0
  157. package/components/tryghost-version-notifications-data-service-5.74.3.tgz +0 -0
  158. package/components/tryghost-webmentions-5.74.3.tgz +0 -0
  159. package/core/built/admin/assets/ghost-ad224505f7a80c242cc1272d1755995d.css +0 -1
  160. package/core/built/admin/assets/ghost-dark-81e3ee175ed67abaafca3fa228c620a3.css +0 -1
  161. /package/core/built/admin/assets/{chunk.761.5cfbe0d0f8ec7aefe7bc.js.LICENSE.txt → chunk.137.0297fba124bc9ba1bbc1.js.LICENSE.txt} +0 -0
@@ -8,7 +8,9 @@ const errors = require('@tryghost/errors');
8
8
 
9
9
  const messages = {
10
10
  nameAlreadyExists: 'A newsletter with the same name already exists',
11
- newsletterNotFound: 'Newsletter not found.'
11
+ newsletterNotFound: 'Newsletter not found.',
12
+ senderEmailNotAllowed: 'You cannot set the sender email address to {email}',
13
+ replyToNotAllowed: 'You cannot set the reply-to email address to {email}'
12
14
  };
13
15
 
14
16
  class NewslettersService {
@@ -21,9 +23,10 @@ class NewslettersService {
21
23
  * @param {Object} options.singleUseTokenProvider
22
24
  * @param {Object} options.urlUtils
23
25
  * @param {ILimitService} options.limitService
26
+ * @param {Object} options.emailAddressService
24
27
  * @param {Object} options.labs
25
28
  */
26
- constructor({NewsletterModel, MemberModel, mail, singleUseTokenProvider, urlUtils, limitService, labs}) {
29
+ constructor({NewsletterModel, MemberModel, mail, singleUseTokenProvider, urlUtils, limitService, labs, emailAddressService}) {
27
30
  this.NewsletterModel = NewsletterModel;
28
31
  this.MemberModel = MemberModel;
29
32
  this.urlUtils = urlUtils;
@@ -31,6 +34,8 @@ class NewslettersService {
31
34
  this.limitService = limitService;
32
35
  /** @private */
33
36
  this.labs = labs;
37
+ /** @private */
38
+ this.emailAddressService = emailAddressService;
34
39
 
35
40
  /* email verification setup */
36
41
 
@@ -243,14 +248,48 @@ class NewslettersService {
243
248
  async prepAttrsForEmailVerification(attrs, newsletter) {
244
249
  const cleanedAttrs = _.cloneDeep(attrs);
245
250
  const emailsToVerify = [];
251
+ const emailProperties = [
252
+ {property: 'sender_email', type: 'from', emptyable: true, error: messages.senderEmailNotAllowed}
253
+ ];
254
+
255
+ if (!this.emailAddressService.service.useNewEmailAddresses) {
256
+ // Validate reply_to is either newsletter or support
257
+ if (cleanedAttrs.sender_reply_to !== undefined) {
258
+ if (!['newsletter', 'support'].includes(cleanedAttrs.sender_reply_to)) {
259
+ throw new errors.ValidationError({
260
+ message: tpl(messages.replyToNotAllowed, {email: cleanedAttrs.sender_reply_to})
261
+ });
262
+ }
263
+ }
264
+ } else {
265
+ if (cleanedAttrs.sender_reply_to !== undefined) {
266
+ if (!['newsletter', 'support'].includes(cleanedAttrs.sender_reply_to)) {
267
+ emailProperties.push({property: 'sender_reply_to', type: 'replyTo', emptyable: false, error: messages.replyToNotAllowed});
268
+ }
269
+ }
270
+ }
246
271
 
247
- for (const property of ['sender_email']) {
272
+ for (const {property, type, emptyable, error} of emailProperties) {
248
273
  const email = cleanedAttrs[property];
249
274
  const hasChanged = !newsletter || newsletter.get(property) !== email;
250
275
 
251
- if (await this.requiresEmailVerification({email, hasChanged})) {
252
- delete cleanedAttrs[property];
253
- emailsToVerify.push({email, property});
276
+ if (hasChanged && email !== undefined) {
277
+ if (email === null || email === '' && emptyable) {
278
+ continue;
279
+ }
280
+
281
+ const validated = this.emailAddressService.service.validate(email, type);
282
+
283
+ if (!validated.allowed) {
284
+ throw new errors.ValidationError({
285
+ message: tpl(error, {email})
286
+ });
287
+ }
288
+
289
+ if (validated.verificationEmailRequired) {
290
+ delete cleanedAttrs[property];
291
+ emailsToVerify.push({email, property});
292
+ }
254
293
  }
255
294
  }
256
295
 
@@ -264,19 +303,6 @@ class NewslettersService {
264
303
  return {cleanedAttrs, emailsToVerify};
265
304
  }
266
305
 
267
- /**
268
- * @private
269
- */
270
- async requiresEmailVerification({email, hasChanged}) {
271
- if (!email || !hasChanged) {
272
- return false;
273
- }
274
-
275
- // TODO: check other newsletters for known/verified email
276
-
277
- return true;
278
- }
279
-
280
306
  /**
281
307
  * @private
282
308
  */
@@ -304,6 +330,13 @@ class NewslettersService {
304
330
  fromEmail = `no-reply@${toDomain}`;
305
331
  }
306
332
 
333
+ if (this.emailAddressService.useNewEmailAddresses) {
334
+ // Gone with the old logic: always use the default email address here
335
+ // We don't need to validate the FROM address, only the to address
336
+ // Also because we are not only validating FROM addresses, but also possible REPLY-TO addresses, which we won't send FROM
337
+ fromEmail = this.emailAddressService.defaultFromAddress;
338
+ }
339
+
307
340
  const {ghostMailer} = this;
308
341
 
309
342
  this.magicLinkService.transporter = {
@@ -5,6 +5,7 @@ const models = require('../../models');
5
5
  const urlUtils = require('../../../shared/url-utils');
6
6
  const limitService = require('../limits');
7
7
  const labs = require('../../../shared/labs');
8
+ const emailAddressService = require('../email-address');
8
9
 
9
10
  const MAGIC_LINK_TOKEN_VALIDITY = 24 * 60 * 60 * 1000;
10
11
  const MAGIC_LINK_TOKEN_VALIDITY_AFTER_USAGE = 10 * 60 * 1000;
@@ -22,5 +23,6 @@ module.exports = new NewslettersService({
22
23
  }),
23
24
  urlUtils,
24
25
  limitService,
25
- labs
26
+ labs,
27
+ emailAddressService: emailAddressService
26
28
  });
@@ -100,6 +100,12 @@ class OfferBookshelfRepository {
100
100
  const count = await this.OfferRedemptionModel.where({offer_id: json.id}).count('id', {
101
101
  transacting: options.transacting
102
102
  });
103
+
104
+ const lastRedeemed = await this.OfferRedemptionModel.where({offer_id: json.id}).orderBy('created_at', 'DESC').fetchAll({
105
+ transacting: options.transacting,
106
+ limit: 1
107
+ });
108
+
103
109
  try {
104
110
  return await Offer.create({
105
111
  id: json.id,
@@ -119,7 +125,8 @@ class OfferBookshelfRepository {
119
125
  id: json.product.id,
120
126
  name: json.product.name
121
127
  },
122
- created_at: json.created_at
128
+ created_at: json.created_at,
129
+ last_redeemed: lastRedeemed.toJSON().length > 0 ? lastRedeemed.toJSON()[0].created_at : null
123
130
  }, null);
124
131
  } catch (err) {
125
132
  logger.error(err);
@@ -92,6 +92,10 @@ module.exports = {
92
92
  fields.push(new CalculatedField({key: 'firstpromoter_account', type: 'string', group: 'firstpromoter', fn: settingsHelpers.getFirstpromoterId.bind(settingsHelpers), dependents: ['firstpromoter', 'firstpromoter_id']}));
93
93
  fields.push(new CalculatedField({key: 'donations_enabled', type: 'boolean', group: 'donations', fn: settingsHelpers.areDonationsEnabled.bind(settingsHelpers), dependents: ['stripe_secret_key', 'stripe_publishable_key', 'stripe_connect_secret_key', 'stripe_connect_publishable_key']}));
94
94
 
95
+ // E-mail addresses
96
+ fields.push(new CalculatedField({key: 'default_email_address', type: 'string', group: 'email', fn: settingsHelpers.getDefaultEmailAddress.bind(settingsHelpers), dependents: ['labs']}));
97
+ fields.push(new CalculatedField({key: 'support_email_address', type: 'string', group: 'email', fn: settingsHelpers.getMembersSupportAddress.bind(settingsHelpers), dependents: ['labs', 'members_support_address']}));
98
+
95
99
  return fields;
96
100
  },
97
101
 
@@ -1,15 +1,18 @@
1
1
  const tpl = require('@tryghost/tpl');
2
2
  const errors = require('@tryghost/errors');
3
+ const {EmailAddressParser} = require('@tryghost/email-addresses');
4
+ const logging = require('@tryghost/logging');
3
5
 
4
6
  const messages = {
5
7
  incorrectKeyType: 'type must be one of "direct" or "connect".'
6
8
  };
7
9
 
8
10
  class SettingsHelpers {
9
- constructor({settingsCache, urlUtils, config}) {
11
+ constructor({settingsCache, urlUtils, config, labs}) {
10
12
  this.settingsCache = settingsCache;
11
13
  this.urlUtils = urlUtils;
12
14
  this.config = config;
15
+ this.labs = labs;
13
16
  }
14
17
 
15
18
  isMembersEnabled() {
@@ -83,7 +86,18 @@ class SettingsHelpers {
83
86
  return this.settingsCache.get('firstpromoter_id');
84
87
  }
85
88
 
89
+ /**
90
+ * @deprecated
91
+ * Please don't make up new email addresses: use the default email addresses
92
+ */
86
93
  getDefaultEmailDomain() {
94
+ if (this.#managedEmailEnabled()) {
95
+ const customSendingDomain = this.#managedSendingDomain();
96
+ if (customSendingDomain) {
97
+ return customSendingDomain;
98
+ }
99
+ }
100
+
87
101
  const url = this.urlUtils.urlFor('home', true).match(new RegExp('^https?://([^/:?#]+)(?:[/:?#]|$)', 'i'));
88
102
  const domain = (url && url[1]) || '';
89
103
  if (domain.startsWith('www.')) {
@@ -93,7 +107,15 @@ class SettingsHelpers {
93
107
  }
94
108
 
95
109
  getMembersSupportAddress() {
96
- const supportAddress = this.settingsCache.get('members_support_address') || 'noreply';
110
+ let supportAddress = this.settingsCache.get('members_support_address');
111
+
112
+ if (!supportAddress && this.useNewEmailAddresses()) {
113
+ // In the new flow, we make a difference between an empty setting (= use default) and a 'noreply' setting (=use noreply @ domain)
114
+ // Also keep the name of the default email!
115
+ return EmailAddressParser.stringify(this.getDefaultEmail());
116
+ }
117
+
118
+ supportAddress = supportAddress || 'noreply';
97
119
 
98
120
  // Any fromAddress without domain uses site domain, like default setting `noreply`
99
121
  if (supportAddress.indexOf('@') < 0) {
@@ -102,13 +124,60 @@ class SettingsHelpers {
102
124
  return supportAddress;
103
125
  }
104
126
 
127
+ /**
128
+ * @deprecated Use getDefaultEmail().address (without name) or EmailAddressParser.stringify(this.getDefaultEmail()) (with name) instead
129
+ */
105
130
  getNoReplyAddress() {
131
+ return this.getDefaultEmailAddress();
132
+ }
133
+
134
+ getDefaultEmailAddress() {
135
+ return this.getDefaultEmail().address;
136
+ }
137
+
138
+ getDefaultEmail() {
139
+ if (this.useNewEmailAddresses()) {
140
+ // parse the email here and remove the sender name
141
+ // E.g. when set to "bar" <from@default.com>
142
+ const configAddress = this.config.get('mail:from');
143
+ const parsed = EmailAddressParser.parse(configAddress);
144
+ if (parsed) {
145
+ return parsed;
146
+ }
147
+
148
+ // For missing configs, we default to the old flow
149
+ logging.warn('Missing mail.from config, falling back to a generated email address. Please update your config file and set a valid from address');
150
+ }
151
+ return {
152
+ address: this.getLegacyNoReplyAddress()
153
+ };
154
+ }
155
+
156
+ /**
157
+ * @deprecated
158
+ * Please start using the new EmailAddressService
159
+ */
160
+ getLegacyNoReplyAddress() {
106
161
  return `noreply@${this.getDefaultEmailDomain()}`;
107
162
  }
108
163
 
109
164
  areDonationsEnabled() {
110
165
  return this.isStripeConnected();
111
166
  }
167
+
168
+ useNewEmailAddresses() {
169
+ return this.#managedEmailEnabled() || this.labs.isSet('newEmailAddresses');
170
+ }
171
+
172
+ // PRIVATE
173
+
174
+ #managedEmailEnabled() {
175
+ return !!this.config.get('hostSettings:managedEmail:enabled');
176
+ }
177
+
178
+ #managedSendingDomain() {
179
+ return this.config.get('hostSettings:managedEmail:sendingDomain');
180
+ }
112
181
  }
113
182
 
114
183
  module.exports = SettingsHelpers;
@@ -2,5 +2,6 @@ const settingsCache = require('../../../shared/settings-cache');
2
2
  const urlUtils = require('../../../shared/url-utils');
3
3
  const config = require('../../../shared/config');
4
4
  const SettingsHelpers = require('./SettingsHelpers');
5
+ const labs = require('../../../shared/labs');
5
6
 
6
- module.exports = new SettingsHelpers({settingsCache, urlUtils, config});
7
+ module.exports = new SettingsHelpers({settingsCache, urlUtils, config, labs});
@@ -124,7 +124,8 @@
124
124
  "signup-form-icon": {"width": 192, "height": 192},
125
125
  "email-header-image": {"width": 1200},
126
126
  "email-latest-posts-image": {"width": 200, "height": 200},
127
- "email-latest-posts-image-mobile": {"width": 1200, "height": 960}
127
+ "email-latest-posts-image-mobile": {"width": 1200, "height": 960},
128
+ "social-image": {"width": 1200}
128
129
  }
129
130
  }
130
131
  }
@@ -46,7 +46,11 @@ const ALPHA_FEATURES = [
46
46
  'importMemberTier',
47
47
  'lexicalIndicators',
48
48
  'editorEmojiPicker',
49
- 'adminXOffers'
49
+ 'adminXOffers',
50
+ 'filterEmailDisabled',
51
+ 'adminXDemo',
52
+ 'tkReminders',
53
+ 'newEmailAddresses'
50
54
  ];
51
55
 
52
56
  module.exports.GA_KEYS = [...GA_FEATURES];
@@ -2,6 +2,59 @@ const config = require('./config');
2
2
  const sentryConfig = config.get('sentry');
3
3
  const errors = require('@tryghost/errors');
4
4
 
5
+ const beforeSend = function (event, hint) {
6
+ try {
7
+ const exception = hint.originalException;
8
+ const code = (exception && exception.code) ? exception.code : null;
9
+ const context = (exception && exception.context) ? exception.context : null;
10
+ const errorType = (exception && exception.errorType) ? exception.errorType : null;
11
+ const id = (exception && exception.id) ? exception.id : null;
12
+ const statusCode = (exception && exception.statusCode) ? exception.statusCode : null;
13
+ event.tags = event.tags || {};
14
+
15
+ if (errors.utils.isGhostError(exception)) {
16
+ // Unexpected errors have a generic error message, set it back to context if there is one
17
+ if (code === 'UNEXPECTED_ERROR' && context !== null) {
18
+ if (event.exception.values && event.exception.values.length > 0) {
19
+ event.exception.values[0].type = context;
20
+ }
21
+ }
22
+
23
+ // This is a mysql2 error — add some additional context
24
+ if (exception.sql) {
25
+ const sql = exception.sql;
26
+ const errno = exception.errno ? exception.errno : null;
27
+ const sqlErrorCode = exception.sqlErrorCode ? exception.sqlErrorCode : null;
28
+ const sqlMessage = exception.sqlMessage ? exception.sqlMessage : null;
29
+ const sqlState = exception.sqlState ? exception.sqlState : null;
30
+ if (event.exception.values && event.exception.values.length > 0) {
31
+ event.exception.values[0].type = `SQL Error ${errno}: ${sqlErrorCode}`;
32
+ event.exception.values[0].value = sqlMessage;
33
+ event.contexts = event.contexts || {};
34
+ event.contexts.mysql = {
35
+ errno: errno,
36
+ code: sqlErrorCode,
37
+ sql: sql,
38
+ message: sqlMessage,
39
+ state: sqlState
40
+ };
41
+ }
42
+ }
43
+
44
+ // This is a Ghost Error, copy all our extra data to tags
45
+ event.tags.type = errorType;
46
+ event.tags.code = code;
47
+ event.tags.id = id;
48
+ event.tags.status_code = statusCode;
49
+ }
50
+ return event;
51
+ } catch (error) {
52
+ // If any errors occur in beforeSend, send the original event to Sentry
53
+ // Better to have some information than no information
54
+ return event;
55
+ }
56
+ };
57
+
5
58
  if (sentryConfig && !sentryConfig.disabled) {
6
59
  const Sentry = require('@sentry/node');
7
60
  const version = require('@tryghost/version').full;
@@ -11,38 +64,7 @@ if (sentryConfig && !sentryConfig.disabled) {
11
64
  release: 'ghost@' + version,
12
65
  environment: environment,
13
66
  maxValueLength: 1000,
14
- beforeSend: function (event, hint) {
15
- const exception = hint.originalException;
16
-
17
- event.tags = event.tags || {};
18
-
19
- if (errors.utils.isGhostError(exception)) {
20
- // Unexpected errors have a generic error message, set it back to context if there is one
21
- if (exception.code === 'UNEXPECTED_ERROR' && exception.context !== null) {
22
- event.exception.values[0].type = exception.context;
23
- }
24
-
25
- // This is a mysql2 error — add some additional context
26
- if (exception.sql) {
27
- event.exception.values[0].type = `SQL Error ${exception.errno}: ${exception.sqlErrorCode}`;
28
- event.exception.values[0].value = exception.sqlMessage;
29
- event.contexts.mysql = {
30
- errno: exception.errno,
31
- code: exception.sqlErrorCode,
32
- sql: exception.sql,
33
- message: exception.sqlMessage,
34
- state: exception.sqlState
35
- };
36
- }
37
-
38
- // This is a Ghost Error, copy all our extra data to tags
39
- event.tags.type = exception.errorType;
40
- event.tags.code = exception.code;
41
- event.tags.id = exception.id;
42
- event.tags.status_code = exception.statusCode;
43
- }
44
- return event;
45
- }
67
+ beforeSend: beforeSend
46
68
  });
47
69
 
48
70
  module.exports = {
@@ -60,7 +82,8 @@ if (sentryConfig && !sentryConfig.disabled) {
60
82
  return (error.statusCode === 500);
61
83
  }
62
84
  }),
63
- captureException: Sentry.captureException
85
+ captureException: Sentry.captureException,
86
+ beforeSend: beforeSend
64
87
  };
65
88
  } else {
66
89
  const expressNoop = function (req, res, next) {