ghost 5.33.8 → 5.34.1

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 (165) hide show
  1. package/components/tryghost-adapter-cache-redis-5.34.1.tgz +0 -0
  2. package/components/tryghost-adapter-manager-5.34.1.tgz +0 -0
  3. package/components/{tryghost-api-framework-5.33.8.tgz → tryghost-api-framework-5.34.1.tgz} +0 -0
  4. package/components/tryghost-api-version-compatibility-service-5.34.1.tgz +0 -0
  5. package/components/tryghost-audience-feedback-5.34.1.tgz +0 -0
  6. package/components/tryghost-bootstrap-socket-5.34.1.tgz +0 -0
  7. package/components/tryghost-constants-5.34.1.tgz +0 -0
  8. package/components/{tryghost-custom-theme-settings-service-5.33.8.tgz → tryghost-custom-theme-settings-service-5.34.1.tgz} +0 -0
  9. package/components/tryghost-data-generator-5.34.1.tgz +0 -0
  10. package/components/tryghost-domain-events-5.34.1.tgz +0 -0
  11. package/components/tryghost-dynamic-routing-events-5.34.1.tgz +0 -0
  12. package/components/tryghost-email-analytics-provider-mailgun-5.34.1.tgz +0 -0
  13. package/components/tryghost-email-analytics-service-5.34.1.tgz +0 -0
  14. package/components/tryghost-email-content-generator-5.34.1.tgz +0 -0
  15. package/components/tryghost-email-events-5.34.1.tgz +0 -0
  16. package/components/tryghost-email-service-5.34.1.tgz +0 -0
  17. package/components/tryghost-email-suppression-list-5.34.1.tgz +0 -0
  18. package/components/{tryghost-express-dynamic-redirects-5.33.8.tgz → tryghost-express-dynamic-redirects-5.34.1.tgz} +0 -0
  19. package/components/tryghost-extract-api-key-5.34.1.tgz +0 -0
  20. package/components/tryghost-html-to-plaintext-5.34.1.tgz +0 -0
  21. package/components/tryghost-i18n-5.34.1.tgz +0 -0
  22. package/components/{tryghost-importer-revue-5.33.8.tgz → tryghost-importer-revue-5.34.1.tgz} +0 -0
  23. package/components/{tryghost-job-manager-5.33.8.tgz → tryghost-job-manager-5.34.1.tgz} +0 -0
  24. package/components/tryghost-link-redirects-5.34.1.tgz +0 -0
  25. package/components/tryghost-link-replacer-5.34.1.tgz +0 -0
  26. package/components/tryghost-link-tracking-5.34.1.tgz +0 -0
  27. package/components/{tryghost-magic-link-5.33.8.tgz → tryghost-magic-link-5.34.1.tgz} +0 -0
  28. package/components/tryghost-mailgun-client-5.34.1.tgz +0 -0
  29. package/components/{tryghost-member-attribution-5.33.8.tgz → tryghost-member-attribution-5.34.1.tgz} +0 -0
  30. package/components/tryghost-member-events-5.34.1.tgz +0 -0
  31. package/components/tryghost-members-api-5.34.1.tgz +0 -0
  32. package/components/tryghost-members-csv-5.34.1.tgz +0 -0
  33. package/components/{tryghost-members-events-service-5.33.8.tgz → tryghost-members-events-service-5.34.1.tgz} +0 -0
  34. package/components/{tryghost-members-importer-5.33.8.tgz → tryghost-members-importer-5.34.1.tgz} +0 -0
  35. package/components/tryghost-members-offers-5.34.1.tgz +0 -0
  36. package/components/tryghost-members-payments-5.34.1.tgz +0 -0
  37. package/components/tryghost-members-ssr-5.34.1.tgz +0 -0
  38. package/components/tryghost-members-stripe-service-5.34.1.tgz +0 -0
  39. package/components/tryghost-milestone-emails-5.34.1.tgz +0 -0
  40. package/components/tryghost-minifier-5.34.1.tgz +0 -0
  41. package/components/tryghost-mw-api-version-mismatch-5.34.1.tgz +0 -0
  42. package/components/{tryghost-mw-cache-control-5.33.8.tgz → tryghost-mw-cache-control-5.34.1.tgz} +0 -0
  43. package/components/{tryghost-mw-error-handler-5.33.8.tgz → tryghost-mw-error-handler-5.34.1.tgz} +0 -0
  44. package/components/tryghost-mw-session-from-token-5.34.1.tgz +0 -0
  45. package/components/tryghost-mw-update-user-last-seen-5.34.1.tgz +0 -0
  46. package/components/tryghost-mw-vhost-5.34.1.tgz +0 -0
  47. package/components/tryghost-oembed-service-5.34.1.tgz +0 -0
  48. package/components/{tryghost-package-json-5.33.8.tgz → tryghost-package-json-5.34.1.tgz} +0 -0
  49. package/components/tryghost-referrers-5.34.1.tgz +0 -0
  50. package/components/tryghost-security-5.34.1.tgz +0 -0
  51. package/components/tryghost-session-service-5.34.1.tgz +0 -0
  52. package/components/tryghost-settings-path-manager-5.34.1.tgz +0 -0
  53. package/components/tryghost-staff-service-5.34.1.tgz +0 -0
  54. package/components/tryghost-stats-service-5.34.1.tgz +0 -0
  55. package/components/tryghost-tags-public-5.34.1.tgz +0 -0
  56. package/components/tryghost-tiers-5.34.1.tgz +0 -0
  57. package/components/{tryghost-update-check-service-5.33.8.tgz → tryghost-update-check-service-5.34.1.tgz} +0 -0
  58. package/components/tryghost-verification-trigger-5.34.1.tgz +0 -0
  59. package/components/tryghost-version-notifications-data-service-5.34.1.tgz +0 -0
  60. package/components/tryghost-webmentions-5.34.1.tgz +0 -0
  61. package/core/boot.js +6 -1
  62. package/core/built/admin/assets/{chunk.143.caa7dbd727b76021c31b.js → chunk.143.07f5af56ff872bb0e9e4.js} +14 -14
  63. package/core/built/admin/assets/{chunk.178.9803e6194593a0ed6595.js → chunk.178.2e831ef9072743e38dd1.js} +4 -4
  64. package/core/built/admin/assets/{chunk.963.e47ead5abeca4cf69fed.js → chunk.616.181e1ad6c33f0bec7a65.js} +3519 -3512
  65. package/core/built/admin/assets/{chunk.963.e47ead5abeca4cf69fed.js.LICENSE.txt → chunk.616.181e1ad6c33f0bec7a65.js.LICENSE.txt} +0 -0
  66. package/core/built/admin/assets/{chunk.79.c3c2c05ea7ff7707fcad.js → chunk.79.ec143a398298020c87e6.js} +112 -112
  67. package/core/built/admin/assets/codemirror/{codemirror-a81c0653d8e57286b75c5a1792f80779.js → codemirror-6c43f4894cbd8db73d7f35cde836c58e.js} +810 -809
  68. package/core/built/admin/assets/{ghost-fb9fb8adbcaf1603ad4006dd2d49e401.css → ghost-558c1e319d6e025bfab2054bc0f7fe83.css} +1 -1
  69. package/core/built/admin/assets/{ghost-872d240189f9b5ff85f4f7cb2ac3ab4c.js → ghost-ad40d109653288e74a7cd922341fb33d.js} +114 -89
  70. package/core/built/admin/assets/{ghost-dark-ac0cb221eddc8652a0e7c263ed6513dc.css → ghost-dark-a15754df1f9070dc2525482ce22e2251.css} +1 -1
  71. package/core/built/admin/assets/simplemde/{simplemde-2885e4a40ed66fcae974595584efe50b.js → simplemde-28049a9bd7f432b0648747eb26958a33.js} +836 -836
  72. package/core/built/admin/assets/{vendor-0441964c34d58f2aacd5a04bbe240243.js → vendor-253d6527ca6353855164ef65f896f762.js} +1208 -1233
  73. package/core/built/admin/index.html +6 -6
  74. package/core/cli/generate-data.js +21 -3
  75. package/core/frontend/services/routing/router-manager.js +1 -1
  76. package/core/frontend/web/middleware/handle-image-sizes.js +6 -21
  77. package/core/server/adapters/cache/Redis.js +3 -0
  78. package/core/server/adapters/storage/LocalStorageBase.js +11 -1
  79. package/core/server/api/endpoints/images.js +47 -6
  80. package/core/server/api/endpoints/mentions.js +1 -1
  81. package/core/server/api/endpoints/tags-public.js +2 -1
  82. package/core/server/api/endpoints/utils/serializers/output/mappers/mentions.js +1 -1
  83. package/core/server/api/endpoints/utils/serializers/output/utils/clean.js +1 -0
  84. package/core/server/data/migrations/versions/5.34/2023-01-30-07-27-add-mentions-permission.js +10 -0
  85. package/core/server/data/migrations/versions/5.34/2023-02-08-03-08-add-mentions-notifications-column.js +7 -0
  86. package/core/server/data/migrations/versions/5.34/2023-02-08-22-32-add-mentions-delete-column.js +7 -0
  87. package/core/server/data/schema/fixtures/fixtures.json +9 -2
  88. package/core/server/data/schema/schema.js +3 -1
  89. package/core/server/lib/image/image-size.js +23 -5
  90. package/core/server/lib/lexical.js +36 -3
  91. package/core/server/models/member.js +3 -0
  92. package/core/server/models/mention.js +7 -1
  93. package/core/server/models/post.js +1 -1
  94. package/core/server/models/user.js +4 -1
  95. package/core/server/services/adapter-manager/index.js +7 -0
  96. package/core/server/services/email-analytics/wrapper.js +22 -13
  97. package/core/server/services/email-service/wrapper.js +1 -1
  98. package/core/server/services/mega/post-email-serializer.js +1 -1
  99. package/core/server/services/members/jobs/index.js +4 -2
  100. package/core/server/services/mentions/BookshelfMentionRepository.js +3 -2
  101. package/core/server/services/mentions/MentionController.js +78 -12
  102. package/core/server/services/mentions/service.js +43 -3
  103. package/core/server/services/milestone-emails/MilestoneQueries.js +58 -0
  104. package/core/server/services/milestone-emails/index.js +1 -0
  105. package/core/server/services/milestone-emails/service.js +58 -0
  106. package/core/server/services/tags-public/index.js +1 -0
  107. package/core/server/services/tags-public/service.js +31 -0
  108. package/core/server/web/api/endpoints/admin/routes.js +0 -1
  109. package/core/server/web/api/middleware/index.js +0 -1
  110. package/core/server/web/shared/middleware/api/spam-prevention.js +31 -1
  111. package/core/server/web/shared/middleware/brute.js +13 -0
  112. package/core/server/web/webmentions/routes.js +3 -0
  113. package/core/shared/config/defaults.json +17 -1
  114. package/core/shared/config/env/config.development.json +0 -3
  115. package/core/shared/config/env/config.testing-browser.json +6 -0
  116. package/core/shared/config/env/config.testing-mysql.json +7 -0
  117. package/core/shared/config/env/config.testing.json +6 -0
  118. package/core/shared/labs.js +3 -3
  119. package/package.json +121 -114
  120. package/yarn.lock +327 -242
  121. package/components/tryghost-adapter-manager-5.33.8.tgz +0 -0
  122. package/components/tryghost-api-version-compatibility-service-5.33.8.tgz +0 -0
  123. package/components/tryghost-audience-feedback-5.33.8.tgz +0 -0
  124. package/components/tryghost-bootstrap-socket-5.33.8.tgz +0 -0
  125. package/components/tryghost-constants-5.33.8.tgz +0 -0
  126. package/components/tryghost-data-generator-5.33.8.tgz +0 -0
  127. package/components/tryghost-domain-events-5.33.8.tgz +0 -0
  128. package/components/tryghost-dynamic-routing-events-5.33.8.tgz +0 -0
  129. package/components/tryghost-email-analytics-provider-mailgun-5.33.8.tgz +0 -0
  130. package/components/tryghost-email-analytics-service-5.33.8.tgz +0 -0
  131. package/components/tryghost-email-content-generator-5.33.8.tgz +0 -0
  132. package/components/tryghost-email-events-5.33.8.tgz +0 -0
  133. package/components/tryghost-email-service-5.33.8.tgz +0 -0
  134. package/components/tryghost-email-suppression-list-5.33.8.tgz +0 -0
  135. package/components/tryghost-extract-api-key-5.33.8.tgz +0 -0
  136. package/components/tryghost-html-to-plaintext-5.33.8.tgz +0 -0
  137. package/components/tryghost-i18n-5.33.8.tgz +0 -0
  138. package/components/tryghost-link-redirects-5.33.8.tgz +0 -0
  139. package/components/tryghost-link-replacer-5.33.8.tgz +0 -0
  140. package/components/tryghost-link-tracking-5.33.8.tgz +0 -0
  141. package/components/tryghost-mailgun-client-5.33.8.tgz +0 -0
  142. package/components/tryghost-member-events-5.33.8.tgz +0 -0
  143. package/components/tryghost-members-api-5.33.8.tgz +0 -0
  144. package/components/tryghost-members-csv-5.33.8.tgz +0 -0
  145. package/components/tryghost-members-offers-5.33.8.tgz +0 -0
  146. package/components/tryghost-members-payments-5.33.8.tgz +0 -0
  147. package/components/tryghost-members-ssr-5.33.8.tgz +0 -0
  148. package/components/tryghost-members-stripe-service-5.33.8.tgz +0 -0
  149. package/components/tryghost-minifier-5.33.8.tgz +0 -0
  150. package/components/tryghost-mw-api-version-mismatch-5.33.8.tgz +0 -0
  151. package/components/tryghost-mw-session-from-token-5.33.8.tgz +0 -0
  152. package/components/tryghost-mw-update-user-last-seen-5.33.8.tgz +0 -0
  153. package/components/tryghost-mw-vhost-5.33.8.tgz +0 -0
  154. package/components/tryghost-oembed-service-5.33.8.tgz +0 -0
  155. package/components/tryghost-referrers-5.33.8.tgz +0 -0
  156. package/components/tryghost-security-5.33.8.tgz +0 -0
  157. package/components/tryghost-session-service-5.33.8.tgz +0 -0
  158. package/components/tryghost-settings-path-manager-5.33.8.tgz +0 -0
  159. package/components/tryghost-staff-service-5.33.8.tgz +0 -0
  160. package/components/tryghost-stats-service-5.33.8.tgz +0 -0
  161. package/components/tryghost-tiers-5.33.8.tgz +0 -0
  162. package/components/tryghost-verification-trigger-5.33.8.tgz +0 -0
  163. package/components/tryghost-version-notifications-data-service-5.33.8.tgz +0 -0
  164. package/components/tryghost-webmentions-5.33.8.tgz +0 -0
  165. package/core/server/web/api/middleware/normalize-image.js +0 -42
@@ -54,7 +54,7 @@ module.exports = class BookshelfMentionRepository {
54
54
  sourceAuthor: model.get('source_author'),
55
55
  sourceExcerpt: model.get('source_excerpt'),
56
56
  sourceFavicon: model.get('source_favicon'),
57
- sourceFeaturedImaged: model.get('source_featured_image')
57
+ sourceFeaturedImage: model.get('source_featured_image')
58
58
  });
59
59
  }
60
60
 
@@ -106,7 +106,8 @@ module.exports = class BookshelfMentionRepository {
106
106
  target: mention.target.href,
107
107
  resource_id: mention.resourceId?.toHexString(),
108
108
  resource_type: mention.resourceId ? 'post' : null,
109
- payload: mention.payload ? JSON.stringify(mention.payload) : null
109
+ payload: mention.payload ? JSON.stringify(mention.payload) : null,
110
+ deleted: Mention.isDeleted(mention)
110
111
  };
111
112
 
112
113
  const existing = await this.#MentionModel.findOne({id: data.id}, {require: false});
@@ -10,17 +10,53 @@ const logging = require('@tryghost/logging');
10
10
  * @typedef {import('@tryghost/webmentions/lib/MentionsAPI').Page} Page<Model>
11
11
  */
12
12
 
13
+ /**
14
+ * @typedef {object} MentionResource
15
+ * @prop {ObjectID} id
16
+ * @prop {string} type
17
+ * @prop {string} name
18
+ */
19
+
20
+ /**
21
+ * @typedef {Mention} MentionDTO
22
+ * @prop {Resource} resource
23
+ */
24
+
25
+ /**
26
+ * @typedef {object} IJobService
27
+ * @prop {(name: string, fn: Function) => void} addJob
28
+ */
29
+
30
+ /**
31
+ * @typedef {object} IMentionResourceService
32
+ * @prop {(id: ObjectID) => Promise<MentionResource>} getByID
33
+ */
34
+
13
35
  module.exports = class MentionController {
14
36
  /** @type {import('@tryghost/webmentions/lib/MentionsAPI')} */
15
37
  #api;
16
38
 
39
+ /** @type {IJobService} */
40
+ #jobService;
41
+
42
+ /** @type {IMentionResourceService} */
43
+ #mentionResourceService;
44
+
45
+ /**
46
+ * @param {object} deps
47
+ * @param {import('@tryghost/webmentions/lib/MentionsAPI')} deps.api
48
+ * @param {IJobService} deps.jobService
49
+ * @param {IMentionResourceService} deps.mentionResourceService
50
+ */
17
51
  async init(deps) {
18
52
  this.#api = deps.api;
53
+ this.#jobService = deps.jobService;
54
+ this.#mentionResourceService = deps.mentionResourceService;
19
55
  }
20
56
 
21
57
  /**
22
58
  * @param {import('@tryghost/api-framework').Frame} frame
23
- * @returns {Promise<Page<Mention>>}
59
+ * @returns {Promise<Page<MentionDTO>>}
24
60
  */
25
61
  async browse(frame) {
26
62
  let limit;
@@ -37,13 +73,41 @@ module.exports = class MentionController {
37
73
  page = 1;
38
74
  }
39
75
 
40
- const results = await this.#api.listMentions({
76
+ let order;
77
+ if (frame.options.order && frame.options.order === 'created_at desc') {
78
+ order = 'created_at desc';
79
+ } else {
80
+ order = 'created_at asc';
81
+ }
82
+
83
+ const mentions = await this.#api.listMentions({
41
84
  filter: frame.options.filter,
85
+ order,
42
86
  limit,
43
87
  page
44
88
  });
45
89
 
46
- return results;
90
+ const resources = await Promise.all(mentions.data.map((mention) => {
91
+ return this.#mentionResourceService.getByID(mention.resourceId);
92
+ }));
93
+
94
+ /** @type {Page<MentionDTO>} */
95
+ const result = {
96
+ data: mentions.data.map((mention, index) => {
97
+ const mentionDTO = {
98
+ ...mention.toJSON(),
99
+ resource: resources[index],
100
+ toJSON() {
101
+ return mentionDTO;
102
+ }
103
+ };
104
+ delete mentionDTO.resourceId;
105
+ return mentionDTO;
106
+ }),
107
+ meta: mentions.meta
108
+ };
109
+
110
+ return result;
47
111
  }
48
112
 
49
113
  /**
@@ -52,15 +116,17 @@ module.exports = class MentionController {
52
116
  */
53
117
  async receive(frame) {
54
118
  logging.info('[Webmention] ' + JSON.stringify(frame.data));
55
- const {source, target, ...payload} = frame.data;
56
- const result = this.#api.processWebmention({
57
- source: new URL(source),
58
- target: new URL(target),
59
- payload
60
- });
61
-
62
- result.catch(function rejected(err) {
63
- logging.error(err);
119
+ this.#jobService.addJob('processWebmention', async () => {
120
+ const {source, target, ...payload} = frame.data;
121
+ try {
122
+ await this.#api.processWebmention({
123
+ source: new URL(source),
124
+ target: new URL(target),
125
+ payload
126
+ });
127
+ } catch (err) {
128
+ logging.error(err);
129
+ }
64
130
  });
65
131
  }
66
132
  };
@@ -13,9 +13,10 @@ const events = require('../../lib/common/events');
13
13
  const externalRequest = require('../../../server/lib/request-external.js');
14
14
  const urlUtils = require('../../../shared/url-utils');
15
15
  const outputSerializerUrlUtil = require('../../../server/api/endpoints/utils/serializers/output/utils/url');
16
- const labs = require('../../../shared/labs');
17
16
  const urlService = require('../url');
17
+ const settingsCache = require('../../../shared/settings-cache');
18
18
  const DomainEvents = require('@tryghost/domain-events');
19
+ const jobsService = require('../jobs');
19
20
 
20
21
  function getPostUrl(post) {
21
22
  const jsonModel = {};
@@ -50,14 +51,53 @@ module.exports = {
50
51
  routingService
51
52
  });
52
53
 
53
- this.controller.init({api});
54
+ this.controller.init({
55
+ api,
56
+ jobService: {
57
+ async addJob(name, fn) {
58
+ jobsService.addJob({
59
+ name,
60
+ job: fn,
61
+ offloaded: false
62
+ });
63
+ }
64
+ },
65
+ mentionResourceService: {
66
+ async getByID(id) {
67
+ if (!id) {
68
+ return null;
69
+ }
70
+
71
+ const post = await models.Post.findOne({id: id.toHexString()});
72
+
73
+ if (!post) {
74
+ return null;
75
+ }
76
+
77
+ return {
78
+ id: id,
79
+ name: post.get('title'),
80
+ type: 'post'
81
+ };
82
+ }
83
+ }
84
+ });
54
85
 
55
86
  const sendingService = new MentionSendingService({
56
87
  discoveryService,
57
88
  externalRequest,
58
89
  getSiteUrl: () => urlUtils.urlFor('home', true),
59
90
  getPostUrl: post => getPostUrl(post),
60
- isEnabled: () => labs.isSet('webmentions')
91
+ isEnabled: () => !settingsCache.get('is_private'),
92
+ jobService: {
93
+ async addJob(name, fn) {
94
+ jobsService.addJob({
95
+ name,
96
+ job: fn,
97
+ offloaded: false
98
+ });
99
+ }
100
+ }
61
101
  });
62
102
  sendingService.listen(events);
63
103
  }
@@ -0,0 +1,58 @@
1
+ const MIN_DAYS_SINCE_IMPORTED = 7;
2
+
3
+ module.exports = class MilestoneQueries {
4
+ #db;
5
+
6
+ constructor(deps) {
7
+ this.#db = deps.db;
8
+ }
9
+
10
+ /**
11
+ * @returns {Promise<number>}
12
+ */
13
+ async getMembersCount() {
14
+ const [membersCount] = await this.#db.knex('members').count('id as count');
15
+
16
+ return membersCount?.count || 0;
17
+ }
18
+
19
+ /**
20
+ * @returns {Promise<Array>}
21
+ */
22
+ async getARR() {
23
+ const currentARR = await this.#db.knex('members_paid_subscription_events as stripe')
24
+ .select(this.#db.knex.raw('ROUND(SUM(stripe.mrr_delta) * 12) / 100 AS arr, stripe.currency as currency'))
25
+ .groupBy('stripe.currency');
26
+
27
+ return currentARR;
28
+ }
29
+
30
+ /**
31
+ * @returns {Promise<boolean>}
32
+ */
33
+ async hasImportedMembersInPeriod() {
34
+ const [hasImportedMembers] = await this.#db.knex('members_subscribe_events')
35
+ .count('id as count')
36
+ .where('source', '=', 'import')
37
+ .where('created_at', '>=', MIN_DAYS_SINCE_IMPORTED);
38
+
39
+ return hasImportedMembers?.count > 0;
40
+ }
41
+
42
+ /**
43
+ * @returns {Promise<string>}
44
+ */
45
+ async getDefaultCurrency() {
46
+ const currentARR = await this.getARR();
47
+
48
+ // Set the default currency as the one with the highest value
49
+ if (currentARR.length > 1) {
50
+ const highestValues = currentARR.sort((a, b) => b.arr - a.arr);
51
+ return highestValues?.[0]?.currency;
52
+ } else if (currentARR?.[0]?.currency) {
53
+ return currentARR[0].currency;
54
+ } else {
55
+ return 'usd';
56
+ }
57
+ }
58
+ };
@@ -0,0 +1 @@
1
+ module.exports = require('./service');
@@ -0,0 +1,58 @@
1
+ // Stubbing stripe in test was causing issues. Moved it
2
+ // into this function to be able to rewire and stub the
3
+ // expected return value.
4
+ const getStripeLiveEnabled = () => {
5
+ const stripeService = require('../stripe');
6
+ // This seems to be the only true way to check if Stripe is configured in live mode
7
+ // settingsCache only cares if Stripe is enabled
8
+ return stripeService.api.configured && stripeService.api.mode === 'live';
9
+ };
10
+
11
+ /**
12
+ *
13
+ * @returns {Promise<any>}
14
+ */
15
+ module.exports = {
16
+ async initAndRun() {
17
+ const labs = require('../../../shared/labs');
18
+
19
+ if (labs.isSet('milestoneEmails')) {
20
+ const db = require('../../data/db');
21
+ const MilestoneQueries = require('./MilestoneQueries');
22
+
23
+ const {
24
+ MilestonesEmailService,
25
+ InMemoryMilestoneRepository
26
+ } = require('@tryghost/milestone-emails');
27
+ const config = require('../../../shared/config');
28
+ const milestonesConfig = config.get('milestones');
29
+ const {GhostMailer} = require('../mail');
30
+
31
+ const mailer = new GhostMailer();
32
+ const repository = new InMemoryMilestoneRepository();
33
+ const queries = new MilestoneQueries({db});
34
+
35
+ const milestonesEmailService = new MilestonesEmailService({
36
+ mailer,
37
+ repository,
38
+ milestonesConfig, // avoid using getters and pass as JSON
39
+ queries
40
+ });
41
+
42
+ let arrResult;
43
+
44
+ // @TODO: schedule recurring jobs instead
45
+ const membersResult = await milestonesEmailService.checkMilestones('members');
46
+ const stripeLiveEnabled = getStripeLiveEnabled();
47
+
48
+ if (stripeLiveEnabled) {
49
+ arrResult = await milestonesEmailService.checkMilestones('arr');
50
+ }
51
+
52
+ return {
53
+ members: membersResult,
54
+ arr: arrResult
55
+ };
56
+ }
57
+ }
58
+ };
@@ -0,0 +1 @@
1
+ module.exports = require('./service');
@@ -0,0 +1,31 @@
1
+ class TagsPublicServiceWrapper {
2
+ async init() {
3
+ if (this.api) {
4
+ // Already done
5
+ return;
6
+ }
7
+
8
+ // Wire up all the dependencies
9
+ const models = require('../../models');
10
+ const adapterManager = require('../adapter-manager');
11
+ const config = require('../../../shared/config');
12
+
13
+ let tagsCache;
14
+ if (config.get('hostSettings:tagsPublicCache:enabled')) {
15
+ tagsCache = adapterManager.getAdapter('cache:tagsPublic');
16
+ }
17
+
18
+ const {TagsPublicRepository} = require('@tryghost/tags-public');
19
+
20
+ this.linkRedirectRepository = new TagsPublicRepository({
21
+ Tag: models.TagPublic,
22
+ cache: tagsCache
23
+ });
24
+
25
+ this.api = {
26
+ browse: this.linkRedirectRepository.getAll.bind(this.linkRedirectRepository)
27
+ };
28
+ }
29
+ }
30
+
31
+ module.exports = new TagsPublicServiceWrapper();
@@ -240,7 +240,6 @@ module.exports = function apiRoutes() {
240
240
  mw.authAdminApi,
241
241
  apiMw.upload.single('file'),
242
242
  apiMw.upload.validation({type: 'images'}),
243
- apiMw.normalizeImage,
244
243
  http(api.images.upload)
245
244
  );
246
245
 
@@ -1,6 +1,5 @@
1
1
  module.exports = {
2
2
  cors: require('./cors'),
3
- normalizeImage: require('./normalize-image'),
4
3
  updateUserLastSeen: require('./update-user-last-seen'),
5
4
  upload: require('./upload'),
6
5
  versionMatch: require('./version-match')
@@ -20,7 +20,8 @@ const messages = {
20
20
  error: 'Only {rateSigninAttempts} tries per IP address every {rateSigninPeriod} seconds.',
21
21
  context: 'Too many login attempts.'
22
22
  },
23
- tooManyAttempts: 'Too many attempts.'
23
+ tooManyAttempts: 'Too many attempts.',
24
+ webmentionsBlock: 'Too many mention attempts'
24
25
  };
25
26
  let spamPrivateBlock = spam.private_block || {};
26
27
  let spamGlobalBlock = spam.global_block || {};
@@ -29,12 +30,14 @@ let spamUserReset = spam.user_reset || {};
29
30
  let spamUserLogin = spam.user_login || {};
30
31
  let spamMemberLogin = spam.member_login || {};
31
32
  let spamContentApiKey = spam.content_api_key || {};
33
+ let spamWebmentionsBlock = spam.webmentions_block || {};
32
34
 
33
35
  let store;
34
36
  let memoryStore;
35
37
  let privateBlogInstance;
36
38
  let globalResetInstance;
37
39
  let globalBlockInstance;
40
+ let webmentionsBlockInstance;
38
41
  let userLoginInstance;
39
42
  let membersAuthInstance;
40
43
  let membersAuthEnumerationInstance;
@@ -123,6 +126,32 @@ const globalReset = () => {
123
126
  return globalResetInstance;
124
127
  };
125
128
 
129
+ const webmentionsBlock = () => {
130
+ const ExpressBrute = require('express-brute');
131
+ const BruteKnex = require('brute-knex');
132
+ const db = require('../../../../data/db');
133
+
134
+ store = store || new BruteKnex({
135
+ tablename: 'brute',
136
+ createTable: false,
137
+ knex: db.knex
138
+ });
139
+
140
+ webmentionsBlockInstance = webmentionsBlockInstance || new ExpressBrute(store,
141
+ extend({
142
+ attachResetToRequest: false,
143
+ failCallback(req, res, next) {
144
+ return next(new errors.TooManyRequestsError({
145
+ message: messages.webmentionsBlock
146
+ }));
147
+ },
148
+ handleStoreError: handleStoreError
149
+ }, pick(spamWebmentionsBlock, spamConfigKeys))
150
+ );
151
+
152
+ return webmentionsBlockInstance;
153
+ };
154
+
126
155
  const membersAuth = () => {
127
156
  const ExpressBrute = require('express-brute');
128
157
  const BruteKnex = require('brute-knex');
@@ -319,6 +348,7 @@ module.exports = {
319
348
  userReset: userReset,
320
349
  privateBlog: privateBlog,
321
350
  contentApiKey: contentApiKey,
351
+ webmentionsBlock: webmentionsBlock,
322
352
  reset: () => {
323
353
  store = undefined;
324
354
  memoryStore = undefined;
@@ -104,5 +104,18 @@ module.exports = {
104
104
  */
105
105
  membersAuthEnumeration(req, res, next) {
106
106
  return spamPrevention.membersAuthEnumeration().prevent(req, res, next);
107
+ },
108
+
109
+ /**
110
+ * Blocks webmention spam
111
+ */
112
+
113
+ webmentionsLimiter(req, res, next) {
114
+ return spamPrevention.webmentionsBlock().getMiddleware({
115
+ ignoreIP: false,
116
+ key(_req, _res, _next) {
117
+ return _next('webmention_blocked');
118
+ }
119
+ })(req, res, next);
107
120
  }
108
121
  };
@@ -11,6 +11,9 @@ module.exports = function apiRoutes() {
11
11
  // shouldn't be cached
12
12
  router.use(shared.middleware.cacheControl('private'));
13
13
 
14
+ // rate limiter
15
+ router.use(shared.middleware.brute.webmentionsLimiter);
16
+
14
17
  // Webmentions
15
18
  router.post('/receive', bodyParser.urlencoded({extended: true, limit: '5mb'}), http(api.mentions.receive));
16
19
 
@@ -102,6 +102,12 @@
102
102
  "maxWait": 43200000,
103
103
  "lifetime": 43200,
104
104
  "freeRetries": 8
105
+ },
106
+ "webmentions_block": {
107
+ "minWait": 10,
108
+ "maxWait": 100,
109
+ "lifetime": 1000,
110
+ "freeRetries": 100
105
111
  }
106
112
  },
107
113
  "caching": {
@@ -198,5 +204,15 @@
198
204
  },
199
205
  "gravatar": {
200
206
  "url": "https://www.gravatar.com/avatar/{hash}?s={size}&r={rating}&d={_default}"
201
- }
207
+ },
208
+ "milestones":
209
+ {
210
+ "arr": [
211
+ {
212
+ "currency": "usd",
213
+ "values": [1000, 10000, 50000, 100000, 250000, 500000, 1000000]
214
+ }
215
+ ],
216
+ "members": [100, 1000, 10000, 50000, 100000, 250000, 500000, 1000000]
217
+ }
202
218
  }
@@ -22,8 +22,5 @@
22
22
  "admin": {
23
23
  "maxAge": 0
24
24
  }
25
- },
26
- "editor": {
27
- "url": "http://127.0.0.1:4173/koenig-lexical.umd.js"
28
25
  }
29
26
  }
@@ -43,6 +43,12 @@
43
43
  "maxWait": 3600000,
44
44
  "lifetime": 3600,
45
45
  "freeRetries":99
46
+ },
47
+ "webmentions_block": {
48
+ "minWait": 100000,
49
+ "maxWait": 100000,
50
+ "lifetime": 3600,
51
+ "freeRetries": 3
46
52
  }
47
53
  },
48
54
  "privacy": {
@@ -44,7 +44,14 @@
44
44
  "maxWait": 3600000,
45
45
  "lifetime": 3600,
46
46
  "freeRetries":99
47
+ },
48
+ "webmentions_block": {
49
+ "minWait": 100000,
50
+ "maxWait": 100000,
51
+ "lifetime": 3600,
52
+ "freeRetries": 3
47
53
  }
54
+
48
55
  },
49
56
  "privacy": {
50
57
  "useTinfoil": true,
@@ -43,6 +43,12 @@
43
43
  "maxWait": 3600000,
44
44
  "lifetime": 3600,
45
45
  "freeRetries":99
46
+ },
47
+ "webmentions_block": {
48
+ "minWait": 100000,
49
+ "maxWait": 100000,
50
+ "lifetime": 3600,
51
+ "freeRetries": 3
46
52
  }
47
53
  },
48
54
  "privacy": {
@@ -27,6 +27,7 @@ const GA_FEATURES = [
27
27
  // input for the "labs" setting value
28
28
  const BETA_FEATURES = [
29
29
  'activitypub',
30
+ 'webmentions',
30
31
  'emailErrors'
31
32
  ];
32
33
 
@@ -34,9 +35,8 @@ const ALPHA_FEATURES = [
34
35
  'urlCache',
35
36
  'beforeAfterCard',
36
37
  'lexicalEditor',
37
- 'webmentions',
38
- 'webmentionEmail',
39
- 'outboundLinkTagging'
38
+ 'outboundLinkTagging',
39
+ 'milestoneEmails'
40
40
  ];
41
41
 
42
42
  module.exports.GA_KEYS = [...GA_FEATURES];