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.
- package/components/{tryghost-adapter-cache-memory-ttl-5.74.3.tgz → tryghost-adapter-cache-memory-ttl-5.74.4.tgz} +0 -0
- package/components/{tryghost-adapter-cache-redis-5.74.3.tgz → tryghost-adapter-cache-redis-5.74.4.tgz} +0 -0
- package/components/{tryghost-adapter-manager-5.74.3.tgz → tryghost-adapter-manager-5.74.4.tgz} +0 -0
- package/components/{tryghost-announcement-bar-settings-5.74.3.tgz → tryghost-announcement-bar-settings-5.74.4.tgz} +0 -0
- package/components/{tryghost-api-framework-5.74.3.tgz → tryghost-api-framework-5.74.4.tgz} +0 -0
- package/components/{tryghost-api-version-compatibility-service-5.74.3.tgz → tryghost-api-version-compatibility-service-5.74.4.tgz} +0 -0
- package/components/{tryghost-audience-feedback-5.74.3.tgz → tryghost-audience-feedback-5.74.4.tgz} +0 -0
- package/components/tryghost-bookshelf-repository-5.74.4.tgz +0 -0
- package/components/tryghost-bootstrap-socket-5.74.4.tgz +0 -0
- package/components/tryghost-collections-5.74.4.tgz +0 -0
- package/components/{tryghost-constants-5.74.3.tgz → tryghost-constants-5.74.4.tgz} +0 -0
- package/components/tryghost-custom-theme-settings-service-5.74.4.tgz +0 -0
- package/components/{tryghost-data-generator-5.74.3.tgz → tryghost-data-generator-5.74.4.tgz} +0 -0
- package/components/tryghost-domain-events-5.74.4.tgz +0 -0
- package/components/tryghost-donations-5.74.4.tgz +0 -0
- package/components/tryghost-dynamic-routing-events-5.74.4.tgz +0 -0
- package/components/tryghost-email-addresses-5.74.4.tgz +0 -0
- package/components/{tryghost-email-analytics-provider-mailgun-5.74.3.tgz → tryghost-email-analytics-provider-mailgun-5.74.4.tgz} +0 -0
- package/components/tryghost-email-analytics-service-5.74.4.tgz +0 -0
- package/components/{tryghost-email-content-generator-5.74.3.tgz → tryghost-email-content-generator-5.74.4.tgz} +0 -0
- package/components/{tryghost-email-events-5.74.3.tgz → tryghost-email-events-5.74.4.tgz} +0 -0
- package/components/tryghost-email-service-5.74.4.tgz +0 -0
- package/components/tryghost-email-suppression-list-5.74.4.tgz +0 -0
- package/components/{tryghost-event-aware-cache-wrapper-5.74.3.tgz → tryghost-event-aware-cache-wrapper-5.74.4.tgz} +0 -0
- package/components/{tryghost-express-dynamic-redirects-5.74.3.tgz → tryghost-express-dynamic-redirects-5.74.4.tgz} +0 -0
- package/components/tryghost-external-media-inliner-5.74.4.tgz +0 -0
- package/components/tryghost-extract-api-key-5.74.4.tgz +0 -0
- package/components/{tryghost-html-to-plaintext-5.74.3.tgz → tryghost-html-to-plaintext-5.74.4.tgz} +0 -0
- package/components/tryghost-i18n-5.74.4.tgz +0 -0
- package/components/tryghost-importer-handler-content-files-5.74.4.tgz +0 -0
- package/components/{tryghost-importer-revue-5.74.3.tgz → tryghost-importer-revue-5.74.4.tgz} +0 -0
- package/components/tryghost-in-memory-repository-5.74.4.tgz +0 -0
- package/components/{tryghost-job-manager-5.74.3.tgz → tryghost-job-manager-5.74.4.tgz} +0 -0
- package/components/{tryghost-link-redirects-5.74.3.tgz → tryghost-link-redirects-5.74.4.tgz} +0 -0
- package/components/{tryghost-link-replacer-5.74.3.tgz → tryghost-link-replacer-5.74.4.tgz} +0 -0
- package/components/{tryghost-link-tracking-5.74.3.tgz → tryghost-link-tracking-5.74.4.tgz} +0 -0
- package/components/{tryghost-magic-link-5.74.3.tgz → tryghost-magic-link-5.74.4.tgz} +0 -0
- package/components/tryghost-mail-events-5.74.4.tgz +0 -0
- package/components/tryghost-mailgun-client-5.74.4.tgz +0 -0
- package/components/tryghost-member-attribution-5.74.4.tgz +0 -0
- package/components/{tryghost-member-events-5.74.3.tgz → tryghost-member-events-5.74.4.tgz} +0 -0
- package/components/{tryghost-members-api-5.74.3.tgz → tryghost-members-api-5.74.4.tgz} +0 -0
- package/components/tryghost-members-csv-5.74.4.tgz +0 -0
- package/components/tryghost-members-events-service-5.74.4.tgz +0 -0
- package/components/{tryghost-members-importer-5.74.3.tgz → tryghost-members-importer-5.74.4.tgz} +0 -0
- package/components/tryghost-members-offers-5.74.4.tgz +0 -0
- package/components/tryghost-members-payments-5.74.4.tgz +0 -0
- package/components/tryghost-members-ssr-5.74.4.tgz +0 -0
- package/components/{tryghost-members-stripe-service-5.74.3.tgz → tryghost-members-stripe-service-5.74.4.tgz} +0 -0
- package/components/{tryghost-mentions-email-report-5.74.3.tgz → tryghost-mentions-email-report-5.74.4.tgz} +0 -0
- package/components/tryghost-milestones-5.74.4.tgz +0 -0
- package/components/tryghost-minifier-5.74.4.tgz +0 -0
- package/components/tryghost-model-to-domain-event-interceptor-5.74.4.tgz +0 -0
- package/components/{tryghost-mw-api-version-mismatch-5.74.3.tgz → tryghost-mw-api-version-mismatch-5.74.4.tgz} +0 -0
- package/components/tryghost-mw-cache-control-5.74.4.tgz +0 -0
- package/components/{tryghost-mw-error-handler-5.74.3.tgz → tryghost-mw-error-handler-5.74.4.tgz} +0 -0
- package/components/tryghost-mw-session-from-token-5.74.4.tgz +0 -0
- package/components/{tryghost-mw-update-user-last-seen-5.74.3.tgz → tryghost-mw-update-user-last-seen-5.74.4.tgz} +0 -0
- package/components/tryghost-mw-version-match-5.74.4.tgz +0 -0
- package/components/tryghost-mw-vhost-5.74.4.tgz +0 -0
- package/components/tryghost-nql-filter-expansions-5.74.4.tgz +0 -0
- package/components/tryghost-oembed-service-5.74.4.tgz +0 -0
- package/components/tryghost-package-json-5.74.4.tgz +0 -0
- package/components/{tryghost-post-events-5.74.3.tgz → tryghost-post-events-5.74.4.tgz} +0 -0
- package/components/tryghost-post-revisions-5.74.4.tgz +0 -0
- package/components/{tryghost-posts-service-5.74.3.tgz → tryghost-posts-service-5.74.4.tgz} +0 -0
- package/components/tryghost-recommendations-5.74.4.tgz +0 -0
- package/components/{tryghost-referrers-5.74.3.tgz → tryghost-referrers-5.74.4.tgz} +0 -0
- package/components/{tryghost-security-5.74.3.tgz → tryghost-security-5.74.4.tgz} +0 -0
- package/components/{tryghost-session-service-5.74.3.tgz → tryghost-session-service-5.74.4.tgz} +0 -0
- package/components/tryghost-settings-path-manager-5.74.4.tgz +0 -0
- package/components/{tryghost-slack-notifications-5.74.3.tgz → tryghost-slack-notifications-5.74.4.tgz} +0 -0
- package/components/tryghost-staff-service-5.74.4.tgz +0 -0
- package/components/tryghost-stats-service-5.74.4.tgz +0 -0
- package/components/{tryghost-tiers-5.74.3.tgz → tryghost-tiers-5.74.4.tgz} +0 -0
- package/components/{tryghost-update-check-service-5.74.3.tgz → tryghost-update-check-service-5.74.4.tgz} +0 -0
- package/components/tryghost-verification-trigger-5.74.4.tgz +0 -0
- package/components/tryghost-version-notifications-data-service-5.74.4.tgz +0 -0
- package/components/tryghost-webmentions-5.74.4.tgz +0 -0
- package/core/boot.js +4 -0
- package/core/built/admin/assets/admin-x-demo/admin-x-demo.js +6 -0
- package/core/built/admin/assets/admin-x-demo/index-2fa5a3c2.mjs +8291 -0
- package/core/built/admin/assets/admin-x-demo/modals-10ef986a.mjs +381 -0
- package/core/built/admin/assets/admin-x-settings/{CodeEditorView-f1193dcf.mjs → CodeEditorView-b41ea5f8.mjs} +76 -76
- package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +2 -2
- package/core/built/admin/assets/admin-x-settings/{index-114d5aab.mjs → index-bc03188e.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/{index-89bf7b3f.mjs → index-e5bc288c.mjs} +8140 -7624
- package/core/built/admin/assets/admin-x-settings/{modals-045fb940.mjs → modals-98e015fa.mjs} +8005 -8080
- package/core/built/admin/assets/{chunk.761.5cfbe0d0f8ec7aefe7bc.js → chunk.137.0297fba124bc9ba1bbc1.js} +691 -686
- package/core/built/admin/assets/{chunk.143.52a7a015be62e953ba15.js → chunk.143.0c31227118654150d49c.js} +5 -5
- package/core/built/admin/assets/{chunk.178.d740bad71c6bc15679c9.js → chunk.178.b2467b9ba07dbbc9016a.js} +4 -4
- package/core/built/admin/assets/ghost-0456ea5c74bd1b27239c11706d1acae9.css +1 -0
- package/core/built/admin/assets/{ghost-ad9952b86c1d886c49a92339adf702e3.js → ghost-7d0f91cd17901ac15ce0ae64a3091e2a.js} +225 -194
- package/core/built/admin/assets/ghost-dark-29863e517e1917a3b0df74979a6cf03f.css +1 -0
- package/core/built/admin/index.html +5 -5
- package/core/frontend/helpers/img_url.js +7 -97
- package/core/frontend/meta/image-dimensions.js +34 -6
- package/core/frontend/utils/images.js +100 -0
- package/core/server/api/endpoints/users.js +34 -5
- package/core/server/data/schema/schema.js +1 -1
- package/core/server/models/base/plugins/events.js +20 -1
- package/core/server/services/email-address/EmailAddressServiceWrapper.js +39 -0
- package/core/server/services/email-address/index.js +3 -0
- package/core/server/services/email-service/EmailServiceWrapper.js +2 -0
- package/core/server/services/mail/GhostMailer.js +46 -9
- package/core/server/services/members/service.js +8 -1
- package/core/server/services/newsletters/NewslettersService.js +52 -19
- package/core/server/services/newsletters/index.js +3 -1
- package/core/server/services/offers/OfferBookshelfRepository.js +8 -1
- package/core/server/services/settings/settings-service.js +4 -0
- package/core/server/services/settings-helpers/SettingsHelpers.js +71 -2
- package/core/server/services/settings-helpers/index.js +2 -1
- package/core/shared/config/overrides.json +2 -1
- package/core/shared/labs.js +5 -1
- package/core/shared/sentry.js +56 -33
- package/package.json +150 -149
- package/yarn.lock +696 -786
- package/components/tryghost-bookshelf-repository-5.74.3.tgz +0 -0
- package/components/tryghost-bootstrap-socket-5.74.3.tgz +0 -0
- package/components/tryghost-collections-5.74.3.tgz +0 -0
- package/components/tryghost-custom-theme-settings-service-5.74.3.tgz +0 -0
- package/components/tryghost-domain-events-5.74.3.tgz +0 -0
- package/components/tryghost-donations-5.74.3.tgz +0 -0
- package/components/tryghost-dynamic-routing-events-5.74.3.tgz +0 -0
- package/components/tryghost-email-analytics-service-5.74.3.tgz +0 -0
- package/components/tryghost-email-service-5.74.3.tgz +0 -0
- package/components/tryghost-email-suppression-list-5.74.3.tgz +0 -0
- package/components/tryghost-external-media-inliner-5.74.3.tgz +0 -0
- package/components/tryghost-extract-api-key-5.74.3.tgz +0 -0
- package/components/tryghost-i18n-5.74.3.tgz +0 -0
- package/components/tryghost-importer-handler-content-files-5.74.3.tgz +0 -0
- package/components/tryghost-in-memory-repository-5.74.3.tgz +0 -0
- package/components/tryghost-mail-events-5.74.3.tgz +0 -0
- package/components/tryghost-mailgun-client-5.74.3.tgz +0 -0
- package/components/tryghost-member-attribution-5.74.3.tgz +0 -0
- package/components/tryghost-members-csv-5.74.3.tgz +0 -0
- package/components/tryghost-members-events-service-5.74.3.tgz +0 -0
- package/components/tryghost-members-offers-5.74.3.tgz +0 -0
- package/components/tryghost-members-payments-5.74.3.tgz +0 -0
- package/components/tryghost-members-ssr-5.74.3.tgz +0 -0
- package/components/tryghost-milestones-5.74.3.tgz +0 -0
- package/components/tryghost-minifier-5.74.3.tgz +0 -0
- package/components/tryghost-model-to-domain-event-interceptor-5.74.3.tgz +0 -0
- package/components/tryghost-mw-cache-control-5.74.3.tgz +0 -0
- package/components/tryghost-mw-session-from-token-5.74.3.tgz +0 -0
- package/components/tryghost-mw-version-match-5.74.3.tgz +0 -0
- package/components/tryghost-mw-vhost-5.74.3.tgz +0 -0
- package/components/tryghost-nql-filter-expansions-5.74.3.tgz +0 -0
- package/components/tryghost-oembed-service-5.74.3.tgz +0 -0
- package/components/tryghost-package-json-5.74.3.tgz +0 -0
- package/components/tryghost-post-revisions-5.74.3.tgz +0 -0
- package/components/tryghost-recommendations-5.74.3.tgz +0 -0
- package/components/tryghost-settings-path-manager-5.74.3.tgz +0 -0
- package/components/tryghost-staff-service-5.74.3.tgz +0 -0
- package/components/tryghost-stats-service-5.74.3.tgz +0 -0
- package/components/tryghost-verification-trigger-5.74.3.tgz +0 -0
- package/components/tryghost-version-notifications-data-service-5.74.3.tgz +0 -0
- package/components/tryghost-webmentions-5.74.3.tgz +0 -0
- package/core/built/admin/assets/ghost-ad224505f7a80c242cc1272d1755995d.css +0 -1
- package/core/built/admin/assets/ghost-dark-81e3ee175ed67abaafca3fa228c620a3.css +0 -1
- /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
|
|
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 (
|
|
252
|
-
|
|
253
|
-
|
|
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
|
-
|
|
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
|
}
|
package/core/shared/labs.js
CHANGED
|
@@ -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];
|
package/core/shared/sentry.js
CHANGED
|
@@ -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:
|
|
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) {
|