ghost 5.112.0 → 5.113.0
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-redis-5.112.0.tgz → tryghost-adapter-cache-redis-5.113.0.tgz} +0 -0
- package/components/{tryghost-adapter-manager-5.112.0.tgz → tryghost-adapter-manager-5.113.0.tgz} +0 -0
- package/components/tryghost-announcement-bar-settings-5.113.0.tgz +0 -0
- package/components/{tryghost-api-framework-5.112.0.tgz → tryghost-api-framework-5.113.0.tgz} +0 -0
- package/components/{tryghost-api-version-compatibility-service-5.112.0.tgz → tryghost-api-version-compatibility-service-5.113.0.tgz} +0 -0
- package/components/{tryghost-audience-feedback-5.112.0.tgz → tryghost-audience-feedback-5.113.0.tgz} +0 -0
- package/components/tryghost-bookshelf-repository-5.113.0.tgz +0 -0
- package/components/tryghost-bootstrap-socket-5.113.0.tgz +0 -0
- package/components/tryghost-captcha-service-5.113.0.tgz +0 -0
- package/components/tryghost-constants-5.113.0.tgz +0 -0
- package/components/tryghost-custom-fonts-5.113.0.tgz +0 -0
- package/components/tryghost-custom-theme-settings-service-5.113.0.tgz +0 -0
- package/components/{tryghost-data-generator-5.112.0.tgz → tryghost-data-generator-5.113.0.tgz} +0 -0
- package/components/tryghost-domain-events-5.113.0.tgz +0 -0
- package/components/{tryghost-donations-5.112.0.tgz → tryghost-donations-5.113.0.tgz} +0 -0
- package/components/tryghost-email-addresses-5.113.0.tgz +0 -0
- package/components/{tryghost-email-analytics-provider-mailgun-5.112.0.tgz → tryghost-email-analytics-provider-mailgun-5.113.0.tgz} +0 -0
- package/components/tryghost-email-analytics-service-5.113.0.tgz +0 -0
- package/components/{tryghost-email-content-generator-5.112.0.tgz → tryghost-email-content-generator-5.113.0.tgz} +0 -0
- package/components/tryghost-email-events-5.113.0.tgz +0 -0
- package/components/{tryghost-email-service-5.112.0.tgz → tryghost-email-service-5.113.0.tgz} +0 -0
- package/components/{tryghost-email-suppression-list-5.112.0.tgz → tryghost-email-suppression-list-5.113.0.tgz} +0 -0
- package/components/{tryghost-express-dynamic-redirects-5.112.0.tgz → tryghost-express-dynamic-redirects-5.113.0.tgz} +0 -0
- package/components/tryghost-extract-api-key-5.113.0.tgz +0 -0
- package/components/{tryghost-ghost-5.112.0.tgz → tryghost-ghost-5.113.0.tgz} +0 -0
- package/components/{tryghost-html-to-plaintext-5.112.0.tgz → tryghost-html-to-plaintext-5.113.0.tgz} +0 -0
- package/components/tryghost-i18n-5.113.0.tgz +0 -0
- package/components/{tryghost-identity-token-service-5.112.0.tgz → tryghost-identity-token-service-5.113.0.tgz} +0 -0
- package/components/{tryghost-importer-handler-content-files-5.112.0.tgz → tryghost-importer-handler-content-files-5.113.0.tgz} +0 -0
- package/components/{tryghost-importer-revue-5.112.0.tgz → tryghost-importer-revue-5.113.0.tgz} +0 -0
- package/components/{tryghost-in-memory-repository-5.112.0.tgz → tryghost-in-memory-repository-5.113.0.tgz} +0 -0
- package/components/tryghost-job-manager-5.113.0.tgz +0 -0
- package/components/{tryghost-link-redirects-5.112.0.tgz → tryghost-link-redirects-5.113.0.tgz} +0 -0
- package/components/{tryghost-link-replacer-5.112.0.tgz → tryghost-link-replacer-5.113.0.tgz} +0 -0
- package/components/{tryghost-magic-link-5.112.0.tgz → tryghost-magic-link-5.113.0.tgz} +0 -0
- package/components/{tryghost-mail-events-5.112.0.tgz → tryghost-mail-events-5.113.0.tgz} +0 -0
- package/components/tryghost-mailgun-client-5.113.0.tgz +0 -0
- package/components/tryghost-member-attribution-5.113.0.tgz +0 -0
- package/components/tryghost-member-events-5.113.0.tgz +0 -0
- package/components/{tryghost-members-api-5.112.0.tgz → tryghost-members-api-5.113.0.tgz} +0 -0
- package/components/tryghost-members-csv-5.113.0.tgz +0 -0
- package/components/{tryghost-members-importer-5.112.0.tgz → tryghost-members-importer-5.113.0.tgz} +0 -0
- package/components/{tryghost-members-offers-5.112.0.tgz → tryghost-members-offers-5.113.0.tgz} +0 -0
- package/components/tryghost-members-payments-5.113.0.tgz +0 -0
- package/components/{tryghost-members-ssr-5.112.0.tgz → tryghost-members-ssr-5.113.0.tgz} +0 -0
- package/components/{tryghost-members-stripe-service-5.112.0.tgz → tryghost-members-stripe-service-5.113.0.tgz} +0 -0
- package/components/tryghost-milestones-5.113.0.tgz +0 -0
- package/components/{tryghost-minifier-5.112.0.tgz → tryghost-minifier-5.113.0.tgz} +0 -0
- package/components/{tryghost-mw-api-version-mismatch-5.112.0.tgz → tryghost-mw-api-version-mismatch-5.113.0.tgz} +0 -0
- package/components/tryghost-mw-cache-control-5.113.0.tgz +0 -0
- package/components/{tryghost-mw-error-handler-5.112.0.tgz → tryghost-mw-error-handler-5.113.0.tgz} +0 -0
- package/components/{tryghost-mw-session-from-token-5.112.0.tgz → tryghost-mw-session-from-token-5.113.0.tgz} +0 -0
- package/components/{tryghost-mw-update-user-last-seen-5.112.0.tgz → tryghost-mw-update-user-last-seen-5.113.0.tgz} +0 -0
- package/components/tryghost-mw-version-match-5.113.0.tgz +0 -0
- package/components/tryghost-mw-vhost-5.113.0.tgz +0 -0
- package/components/{tryghost-package-json-5.112.0.tgz → tryghost-package-json-5.113.0.tgz} +0 -0
- package/components/{tryghost-post-events-5.112.0.tgz → tryghost-post-events-5.113.0.tgz} +0 -0
- package/components/tryghost-post-revisions-5.113.0.tgz +0 -0
- package/components/{tryghost-posts-service-5.112.0.tgz → tryghost-posts-service-5.113.0.tgz} +0 -0
- package/components/{tryghost-prometheus-metrics-5.112.0.tgz → tryghost-prometheus-metrics-5.113.0.tgz} +0 -0
- package/components/tryghost-recommendations-5.113.0.tgz +0 -0
- package/components/tryghost-referrers-5.113.0.tgz +0 -0
- package/components/{tryghost-security-5.112.0.tgz → tryghost-security-5.113.0.tgz} +0 -0
- package/components/tryghost-session-service-5.113.0.tgz +0 -0
- package/components/{tryghost-settings-path-manager-5.112.0.tgz → tryghost-settings-path-manager-5.113.0.tgz} +0 -0
- package/components/{tryghost-slack-notifications-5.112.0.tgz → tryghost-slack-notifications-5.113.0.tgz} +0 -0
- package/components/tryghost-tiers-5.113.0.tgz +0 -0
- package/components/{tryghost-version-notifications-data-service-5.112.0.tgz → tryghost-version-notifications-data-service-5.113.0.tgz} +0 -0
- package/components/{tryghost-webmentions-5.112.0.tgz → tryghost-webmentions-5.113.0.tgz} +0 -0
- package/core/built/admin/assets/admin-x-activitypub/admin-x-activitypub.js +12953 -12243
- package/core/built/admin/assets/admin-x-settings/{CodeEditorView-ad8698fe.mjs → CodeEditorView-ed5e87be.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +1 -1
- package/core/built/admin/assets/admin-x-settings/{index-463cec50.mjs → index-0ee4d13c.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/{index-2713e469.mjs → index-9c7da716.mjs} +19975 -19965
- package/core/built/admin/assets/admin-x-settings/{modals-033e8fc4.mjs → modals-7708d510.mjs} +2226 -2215
- package/core/built/admin/assets/{chunk.524.db49da6fd8ae155205a4.js → chunk.524.4f0aeb6b611079e528f5.js} +7 -7
- package/core/built/admin/assets/{chunk.582.0bf715eb6807f7641706.js → chunk.582.485df00698ed27a0668b.js} +9 -9
- package/core/built/admin/assets/{ghost-62bd4d4c837d453e1038808dc1cd1e4c.js → ghost-ebf07ae7768b6e9fb9a4b173b6917782.js} +66 -66
- package/core/built/admin/assets/posts/posts.js +1 -1
- package/core/built/admin/assets/{vendor-fca15534b8426c0567400113c63a3e21.js → vendor-68a4aa424a179a90f5bbc2b750def576.js} +28 -26
- package/core/built/admin/index.html +4 -4
- package/core/frontend/services/routing/registry.js +6 -6
- package/core/frontend/src/admin-auth/message-handler.js +1 -1
- package/core/server/adapters/cache/AdapterCacheMemoryTTL.js +54 -0
- package/core/server/adapters/cache/memory-ttl.js +1 -1
- package/core/server/data/migrations/versions/5.113/2025-03-07-12-24-00-add-super-editor.js +31 -0
- package/core/server/data/migrations/versions/5.113/2025-03-07-12-25-00-add-member-perms-to-super-editor.js +291 -0
- package/core/server/data/schema/fixtures/fixtures.json +27 -0
- package/core/server/models/invite.js +2 -2
- package/core/server/models/role.js +2 -2
- package/core/server/models/user.js +39 -28
- package/core/server/services/email-analytics/jobs/update-member-email-analytics/index.js +13 -0
- package/core/server/services/email-analytics/lib/queries.js +3 -3
- package/core/server/services/media-inliner/ExternalMediaInliner.js +346 -0
- package/core/server/services/media-inliner/service.js +1 -1
- package/core/server/services/permissions/can-this.js +3 -2
- package/core/server/services/url/Resources.js +19 -29
- package/core/server/services/url/UrlService.js +2 -12
- package/core/server/services/url/Urls.js +17 -33
- package/core/shared/config/defaults.json +1 -1
- package/core/shared/labs.js +2 -1
- package/core/shared/settings-cache/CacheManager.js +4 -4
- package/package.json +134 -134
- package/yarn.lock +10 -10
- package/components/tryghost-adapter-cache-memory-ttl-5.112.0.tgz +0 -0
- package/components/tryghost-announcement-bar-settings-5.112.0.tgz +0 -0
- package/components/tryghost-bookshelf-repository-5.112.0.tgz +0 -0
- package/components/tryghost-bootstrap-socket-5.112.0.tgz +0 -0
- package/components/tryghost-captcha-service-5.112.0.tgz +0 -0
- package/components/tryghost-constants-5.112.0.tgz +0 -0
- package/components/tryghost-custom-fonts-5.112.0.tgz +0 -0
- package/components/tryghost-custom-theme-settings-service-5.112.0.tgz +0 -0
- package/components/tryghost-domain-events-5.112.0.tgz +0 -0
- package/components/tryghost-email-addresses-5.112.0.tgz +0 -0
- package/components/tryghost-email-analytics-service-5.112.0.tgz +0 -0
- package/components/tryghost-email-events-5.112.0.tgz +0 -0
- package/components/tryghost-external-media-inliner-5.112.0.tgz +0 -0
- package/components/tryghost-extract-api-key-5.112.0.tgz +0 -0
- package/components/tryghost-i18n-5.112.0.tgz +0 -0
- package/components/tryghost-job-manager-5.112.0.tgz +0 -0
- package/components/tryghost-mailgun-client-5.112.0.tgz +0 -0
- package/components/tryghost-member-attribution-5.112.0.tgz +0 -0
- package/components/tryghost-member-events-5.112.0.tgz +0 -0
- package/components/tryghost-members-csv-5.112.0.tgz +0 -0
- package/components/tryghost-members-payments-5.112.0.tgz +0 -0
- package/components/tryghost-milestones-5.112.0.tgz +0 -0
- package/components/tryghost-mw-cache-control-5.112.0.tgz +0 -0
- package/components/tryghost-mw-version-match-5.112.0.tgz +0 -0
- package/components/tryghost-mw-vhost-5.112.0.tgz +0 -0
- package/components/tryghost-post-revisions-5.112.0.tgz +0 -0
- package/components/tryghost-recommendations-5.112.0.tgz +0 -0
- package/components/tryghost-referrers-5.112.0.tgz +0 -0
- package/components/tryghost-session-service-5.112.0.tgz +0 -0
- package/components/tryghost-tiers-5.112.0.tgz +0 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const TTLCache = require('@isaacs/ttlcache');
|
|
2
|
+
const Base = require('@tryghost/adapter-base-cache');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Cache adapter compatible wrapper around TTLCache
|
|
6
|
+
* Distinct features of this cache adapter:
|
|
7
|
+
* - it is in-memory only
|
|
8
|
+
* - it supports time-to-live (TTL)
|
|
9
|
+
* - it supports a max number of items
|
|
10
|
+
*/
|
|
11
|
+
class AdapterCacheMemoryTTL extends Base {
|
|
12
|
+
#cache;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
*
|
|
16
|
+
* @param {Object} [deps]
|
|
17
|
+
* @param {Number} [deps.max] - The max number of items to keep in the cache.
|
|
18
|
+
* @param {Number} [deps.ttl] - The max time in ms to store items
|
|
19
|
+
*/
|
|
20
|
+
constructor({max = Infinity, ttl = Infinity} = {}) {
|
|
21
|
+
super();
|
|
22
|
+
|
|
23
|
+
this.#cache = new TTLCache({max, ttl});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get(key) {
|
|
27
|
+
return this.#cache.get(key);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
*
|
|
32
|
+
* @param {String} key
|
|
33
|
+
* @param {*} value
|
|
34
|
+
* @param {Object} [options]
|
|
35
|
+
* @param {Number} [options.ttl]
|
|
36
|
+
*/
|
|
37
|
+
set(key, value, {ttl} = {}) {
|
|
38
|
+
this.#cache.set(key, value, {ttl});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
reset() {
|
|
42
|
+
this.#cache.clear();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Helper method to assist "getAll" type of operations
|
|
47
|
+
* @returns {Array<String>} all keys present in the cache
|
|
48
|
+
*/
|
|
49
|
+
keys() {
|
|
50
|
+
return [...this.#cache.keys()];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
module.exports = AdapterCacheMemoryTTL;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const logging = require('@tryghost/logging');
|
|
2
|
+
const {default: ObjectID} = require('bson-objectid');
|
|
3
|
+
const {createTransactionalMigration, meta} = require('../../utils');
|
|
4
|
+
|
|
5
|
+
module.exports = createTransactionalMigration(
|
|
6
|
+
async function up(knex) {
|
|
7
|
+
logging.info('Creating "Super Editor" role');
|
|
8
|
+
const existingRole = await knex('roles').where({
|
|
9
|
+
name: 'Super Editor'
|
|
10
|
+
}).first();
|
|
11
|
+
|
|
12
|
+
if (existingRole) {
|
|
13
|
+
logging.warn('"Super Editor" role already exists, skipping');
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
await knex('roles').insert({
|
|
18
|
+
id: (new ObjectID()).toHexString(),
|
|
19
|
+
name: 'Super Editor',
|
|
20
|
+
description: 'Editor plus member management',
|
|
21
|
+
created_by: meta.MIGRATION_USER,
|
|
22
|
+
created_at: knex.raw('current_timestamp')
|
|
23
|
+
});
|
|
24
|
+
},
|
|
25
|
+
async function down(knex) {
|
|
26
|
+
logging.info('Deleting role "Super Editor"');
|
|
27
|
+
await knex('roles').where({
|
|
28
|
+
name: 'Super Editor'
|
|
29
|
+
}).del();
|
|
30
|
+
}
|
|
31
|
+
);
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
const {addPermissionToRole, combineTransactionalMigrations} = require('../../utils');
|
|
2
|
+
module.exports = combineTransactionalMigrations(
|
|
3
|
+
addPermissionToRole({
|
|
4
|
+
permission: 'Browse Members',
|
|
5
|
+
role: 'Super Editor'
|
|
6
|
+
}),
|
|
7
|
+
addPermissionToRole({
|
|
8
|
+
permission: 'Read Members',
|
|
9
|
+
role: 'Super Editor'
|
|
10
|
+
}),
|
|
11
|
+
addPermissionToRole({
|
|
12
|
+
permission: 'Edit Members',
|
|
13
|
+
role: 'Super Editor'
|
|
14
|
+
}),
|
|
15
|
+
addPermissionToRole({
|
|
16
|
+
permission: 'Add Members',
|
|
17
|
+
role: 'Super Editor'
|
|
18
|
+
}),
|
|
19
|
+
addPermissionToRole({
|
|
20
|
+
permission: 'Delete Members',
|
|
21
|
+
role: 'Super Editor'
|
|
22
|
+
}),
|
|
23
|
+
addPermissionToRole({
|
|
24
|
+
permission: 'Read offers',
|
|
25
|
+
role: 'Super Editor'
|
|
26
|
+
}),
|
|
27
|
+
addPermissionToRole({
|
|
28
|
+
permission: 'Browse offers',
|
|
29
|
+
role: 'Super Editor'
|
|
30
|
+
}),
|
|
31
|
+
addPermissionToRole({
|
|
32
|
+
permission: 'Read member signin urls',
|
|
33
|
+
role: 'Super Editor'
|
|
34
|
+
}),
|
|
35
|
+
addPermissionToRole({
|
|
36
|
+
permission: 'Browse notifications',
|
|
37
|
+
role: 'Super Editor'
|
|
38
|
+
}),
|
|
39
|
+
addPermissionToRole({
|
|
40
|
+
permission: 'Add notifications',
|
|
41
|
+
role: 'Super Editor'
|
|
42
|
+
}),
|
|
43
|
+
addPermissionToRole({
|
|
44
|
+
permission: 'Delete notifications',
|
|
45
|
+
role: 'Super Editor'
|
|
46
|
+
}),
|
|
47
|
+
addPermissionToRole({
|
|
48
|
+
permission: 'Generate slugs',
|
|
49
|
+
role: 'Super Editor'
|
|
50
|
+
}),
|
|
51
|
+
addPermissionToRole({
|
|
52
|
+
permission: 'Browse posts',
|
|
53
|
+
role: 'Super Editor'
|
|
54
|
+
}),
|
|
55
|
+
addPermissionToRole({
|
|
56
|
+
permission: 'Read posts',
|
|
57
|
+
role: 'Super Editor'
|
|
58
|
+
}),
|
|
59
|
+
addPermissionToRole({
|
|
60
|
+
permission: 'Edit posts',
|
|
61
|
+
role: 'Super Editor'
|
|
62
|
+
}),
|
|
63
|
+
addPermissionToRole({
|
|
64
|
+
permission: 'Add posts',
|
|
65
|
+
role: 'Super Editor'
|
|
66
|
+
}),
|
|
67
|
+
addPermissionToRole({
|
|
68
|
+
permission: 'Delete posts',
|
|
69
|
+
role: 'Super Editor'
|
|
70
|
+
}),
|
|
71
|
+
addPermissionToRole({
|
|
72
|
+
permission: 'Publish posts',
|
|
73
|
+
role: 'Super Editor'
|
|
74
|
+
}),
|
|
75
|
+
addPermissionToRole({
|
|
76
|
+
permission: 'Browse settings',
|
|
77
|
+
role: 'Super Editor'
|
|
78
|
+
}),
|
|
79
|
+
addPermissionToRole({
|
|
80
|
+
permission: 'Read settings',
|
|
81
|
+
role: 'Super Editor'
|
|
82
|
+
}),
|
|
83
|
+
addPermissionToRole({
|
|
84
|
+
permission: 'Browse tags',
|
|
85
|
+
role: 'Super Editor'
|
|
86
|
+
}),
|
|
87
|
+
addPermissionToRole({
|
|
88
|
+
permission: 'Read tags',
|
|
89
|
+
role: 'Super Editor'
|
|
90
|
+
}),
|
|
91
|
+
addPermissionToRole({
|
|
92
|
+
permission: 'Edit tags',
|
|
93
|
+
role: 'Super Editor'
|
|
94
|
+
}),
|
|
95
|
+
addPermissionToRole({
|
|
96
|
+
permission: 'Add tags',
|
|
97
|
+
role: 'Super Editor'
|
|
98
|
+
}),
|
|
99
|
+
addPermissionToRole({
|
|
100
|
+
permission: 'Delete tags',
|
|
101
|
+
role: 'Super Editor'
|
|
102
|
+
}),
|
|
103
|
+
addPermissionToRole({
|
|
104
|
+
permission: 'Browse themes',
|
|
105
|
+
role: 'Super Editor'
|
|
106
|
+
}),
|
|
107
|
+
addPermissionToRole({
|
|
108
|
+
permission: 'View active theme details',
|
|
109
|
+
role: 'Super Editor'
|
|
110
|
+
}),
|
|
111
|
+
addPermissionToRole({
|
|
112
|
+
permission: 'Browse users',
|
|
113
|
+
role: 'Super Editor'
|
|
114
|
+
}),
|
|
115
|
+
addPermissionToRole({
|
|
116
|
+
permission: 'Read users',
|
|
117
|
+
role: 'Super Editor'
|
|
118
|
+
}),
|
|
119
|
+
addPermissionToRole({
|
|
120
|
+
permission: 'Edit users',
|
|
121
|
+
role: 'Super Editor'
|
|
122
|
+
}),
|
|
123
|
+
addPermissionToRole({
|
|
124
|
+
permission: 'Add users',
|
|
125
|
+
role: 'Super Editor'
|
|
126
|
+
}),
|
|
127
|
+
addPermissionToRole({
|
|
128
|
+
permission: 'Delete users',
|
|
129
|
+
role: 'Super Editor'
|
|
130
|
+
}),
|
|
131
|
+
addPermissionToRole({
|
|
132
|
+
permission: 'Assign a role',
|
|
133
|
+
role: 'Super Editor'
|
|
134
|
+
}),
|
|
135
|
+
addPermissionToRole({
|
|
136
|
+
permission: 'Browse roles',
|
|
137
|
+
role: 'Super Editor'
|
|
138
|
+
}),
|
|
139
|
+
addPermissionToRole({
|
|
140
|
+
permission: 'Browse invites',
|
|
141
|
+
role: 'Super Editor'
|
|
142
|
+
}),
|
|
143
|
+
addPermissionToRole({
|
|
144
|
+
permission: 'Read invites',
|
|
145
|
+
role: 'Super Editor'
|
|
146
|
+
}),
|
|
147
|
+
addPermissionToRole({
|
|
148
|
+
permission: 'Add invites',
|
|
149
|
+
role: 'Super Editor'
|
|
150
|
+
}),
|
|
151
|
+
addPermissionToRole({
|
|
152
|
+
permission: 'Delete invites',
|
|
153
|
+
role: 'Super Editor'
|
|
154
|
+
}),
|
|
155
|
+
addPermissionToRole({
|
|
156
|
+
permission: 'Edit invites',
|
|
157
|
+
role: 'Super Editor'
|
|
158
|
+
}),
|
|
159
|
+
addPermissionToRole({
|
|
160
|
+
permission: 'Email preview',
|
|
161
|
+
role: 'Super Editor'
|
|
162
|
+
}),
|
|
163
|
+
addPermissionToRole({
|
|
164
|
+
permission: 'Send test email',
|
|
165
|
+
role: 'Super Editor'
|
|
166
|
+
}),
|
|
167
|
+
addPermissionToRole({
|
|
168
|
+
permission: 'Read emails',
|
|
169
|
+
role: 'Super Editor'
|
|
170
|
+
}),
|
|
171
|
+
addPermissionToRole({
|
|
172
|
+
permission: 'Browse emails',
|
|
173
|
+
role: 'Super Editor'
|
|
174
|
+
}),
|
|
175
|
+
addPermissionToRole({
|
|
176
|
+
permission: 'Retry emails',
|
|
177
|
+
role: 'Super Editor'
|
|
178
|
+
}),
|
|
179
|
+
addPermissionToRole({
|
|
180
|
+
permission: 'Browse snippets',
|
|
181
|
+
role: 'Super Editor'
|
|
182
|
+
}),
|
|
183
|
+
addPermissionToRole({
|
|
184
|
+
permission: 'Read snippets',
|
|
185
|
+
role: 'Super Editor'
|
|
186
|
+
}),
|
|
187
|
+
addPermissionToRole({
|
|
188
|
+
permission: 'Edit snippets',
|
|
189
|
+
role: 'Super Editor'
|
|
190
|
+
}),
|
|
191
|
+
addPermissionToRole({
|
|
192
|
+
permission: 'Add snippets',
|
|
193
|
+
role: 'Super Editor'
|
|
194
|
+
}),
|
|
195
|
+
addPermissionToRole({
|
|
196
|
+
permission: 'Delete snippets',
|
|
197
|
+
role: 'Super Editor'
|
|
198
|
+
}),
|
|
199
|
+
addPermissionToRole({
|
|
200
|
+
permission: 'Browse labels',
|
|
201
|
+
role: 'Super Editor'
|
|
202
|
+
}),
|
|
203
|
+
addPermissionToRole({
|
|
204
|
+
permission: 'Read labels',
|
|
205
|
+
role: 'Super Editor'
|
|
206
|
+
}),
|
|
207
|
+
addPermissionToRole({
|
|
208
|
+
permission: 'Edit labels',
|
|
209
|
+
role: 'Super Editor'
|
|
210
|
+
}),
|
|
211
|
+
addPermissionToRole({
|
|
212
|
+
permission: 'Add labels',
|
|
213
|
+
role: 'Super Editor'
|
|
214
|
+
}),
|
|
215
|
+
addPermissionToRole({
|
|
216
|
+
permission: 'Delete labels',
|
|
217
|
+
role: 'Super Editor'
|
|
218
|
+
}),
|
|
219
|
+
addPermissionToRole({
|
|
220
|
+
permission: 'Browse Products',
|
|
221
|
+
role: 'Super Editor'
|
|
222
|
+
}),
|
|
223
|
+
addPermissionToRole({
|
|
224
|
+
permission: 'Read Products',
|
|
225
|
+
role: 'Super Editor'
|
|
226
|
+
}),
|
|
227
|
+
addPermissionToRole({
|
|
228
|
+
permission: 'Browse newsletters',
|
|
229
|
+
role: 'Super Editor'
|
|
230
|
+
}),
|
|
231
|
+
addPermissionToRole({
|
|
232
|
+
permission: 'Read newsletters',
|
|
233
|
+
role: 'Super Editor'
|
|
234
|
+
}),
|
|
235
|
+
addPermissionToRole({
|
|
236
|
+
permission: 'Browse collections',
|
|
237
|
+
role: 'Super Editor'
|
|
238
|
+
}),
|
|
239
|
+
addPermissionToRole({
|
|
240
|
+
permission: 'Read collections',
|
|
241
|
+
role: 'Super Editor'
|
|
242
|
+
}),
|
|
243
|
+
addPermissionToRole({
|
|
244
|
+
permission: 'Edit collections',
|
|
245
|
+
role: 'Super Editor'
|
|
246
|
+
}),
|
|
247
|
+
addPermissionToRole({
|
|
248
|
+
permission: 'Add collections',
|
|
249
|
+
role: 'Super Editor'
|
|
250
|
+
}),
|
|
251
|
+
addPermissionToRole({
|
|
252
|
+
permission: 'Delete collections',
|
|
253
|
+
role: 'Super Editor'
|
|
254
|
+
}),
|
|
255
|
+
addPermissionToRole({
|
|
256
|
+
permission: 'Moderate comments',
|
|
257
|
+
role: 'Super Editor'
|
|
258
|
+
}),
|
|
259
|
+
addPermissionToRole({
|
|
260
|
+
permission: 'Like comments',
|
|
261
|
+
role: 'Super Editor'
|
|
262
|
+
}),
|
|
263
|
+
addPermissionToRole({
|
|
264
|
+
permission: 'Unlike comments',
|
|
265
|
+
role: 'Super Editor'
|
|
266
|
+
}),
|
|
267
|
+
addPermissionToRole({
|
|
268
|
+
permission: 'Add comments',
|
|
269
|
+
role: 'Super Editor'
|
|
270
|
+
}),
|
|
271
|
+
addPermissionToRole({
|
|
272
|
+
permission: 'Edit comments',
|
|
273
|
+
role: 'Super Editor'
|
|
274
|
+
}),
|
|
275
|
+
addPermissionToRole({
|
|
276
|
+
permission: 'Delete comments',
|
|
277
|
+
role: 'Super Editor'
|
|
278
|
+
}),
|
|
279
|
+
addPermissionToRole({
|
|
280
|
+
permission: 'Read comments',
|
|
281
|
+
role: 'Super Editor'
|
|
282
|
+
}),
|
|
283
|
+
addPermissionToRole({
|
|
284
|
+
permission: 'Browse comments',
|
|
285
|
+
role: 'Super Editor'
|
|
286
|
+
}),
|
|
287
|
+
addPermissionToRole({
|
|
288
|
+
permission: 'Report comments',
|
|
289
|
+
role: 'Super Editor'
|
|
290
|
+
})
|
|
291
|
+
);
|
|
@@ -106,6 +106,10 @@
|
|
|
106
106
|
{
|
|
107
107
|
"name": "Scheduler Integration",
|
|
108
108
|
"description": "Internal Scheduler Client"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"name": "Super Editor",
|
|
112
|
+
"description": "Super Editors"
|
|
109
113
|
}
|
|
110
114
|
]
|
|
111
115
|
},
|
|
@@ -917,6 +921,29 @@
|
|
|
917
921
|
"recommendation": "all",
|
|
918
922
|
"member_signin_url": "read"
|
|
919
923
|
},
|
|
924
|
+
"Super Editor": {
|
|
925
|
+
"notification": "all",
|
|
926
|
+
"post": "all",
|
|
927
|
+
"setting": ["browse", "read"],
|
|
928
|
+
"slug": "all",
|
|
929
|
+
"tag": "all",
|
|
930
|
+
"user": "all",
|
|
931
|
+
"role": "all",
|
|
932
|
+
"invite": "all",
|
|
933
|
+
"theme": ["browse", "readActive"],
|
|
934
|
+
"email_preview": "all",
|
|
935
|
+
"email": "all",
|
|
936
|
+
"snippet": "all",
|
|
937
|
+
"label": ["browse", "read", "edit", "add", "destroy"],
|
|
938
|
+
"product": ["browse", "read"],
|
|
939
|
+
"newsletter": ["browse", "read"],
|
|
940
|
+
"collection": "all",
|
|
941
|
+
"recommendation": ["browse", "read"],
|
|
942
|
+
"member": ["browse", "read", "add", "edit", "destroy"],
|
|
943
|
+
"member_signin_url": "read",
|
|
944
|
+
"offer": ["browse", "read"],
|
|
945
|
+
"comment": "all"
|
|
946
|
+
},
|
|
920
947
|
"Editor": {
|
|
921
948
|
"notification": "all",
|
|
922
949
|
"post": "all",
|
|
@@ -87,12 +87,12 @@ Invite = ghostBookshelf.Model.extend({
|
|
|
87
87
|
if (loadedPermissions.user) {
|
|
88
88
|
const {isOwner, isAdmin, isEitherEditor} = setIsRoles(loadedPermissions);
|
|
89
89
|
if (isOwner || isAdmin) {
|
|
90
|
-
allowed = ['Administrator', 'Editor', 'Author', 'Contributor'];
|
|
90
|
+
allowed = ['Administrator', 'Editor', 'Author', 'Contributor', 'Super Editor'];
|
|
91
91
|
} else if (isEitherEditor) {
|
|
92
92
|
allowed = ['Author', 'Contributor'];
|
|
93
93
|
}
|
|
94
94
|
} else if (loadedPermissions.apiKey) {
|
|
95
|
-
allowed = ['Editor', 'Author', 'Contributor'];
|
|
95
|
+
allowed = ['Editor', 'Author', 'Contributor', 'Super Editor'];
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
if (allowed.indexOf(roleToInvite.get('name')) === -1) {
|
|
@@ -82,9 +82,9 @@ Role = ghostBookshelf.Model.extend({
|
|
|
82
82
|
const {isOwner, isAdmin, isEitherEditor} = setIsRoles(loadedPermissions);
|
|
83
83
|
let checkAgainst;
|
|
84
84
|
if (isOwner) {
|
|
85
|
-
checkAgainst = ['Owner', 'Administrator', 'Editor', 'Author', 'Contributor'];
|
|
85
|
+
checkAgainst = ['Owner', 'Administrator', 'Super Editor', 'Editor', 'Author', 'Contributor'];
|
|
86
86
|
} else if (isAdmin) {
|
|
87
|
-
checkAgainst = ['Administrator', 'Editor', 'Author', 'Contributor'];
|
|
87
|
+
checkAgainst = ['Administrator', 'Super Editor', 'Editor', 'Author', 'Contributor'];
|
|
88
88
|
} else if (isEitherEditor) {
|
|
89
89
|
checkAgainst = ['Author', 'Contributor'];
|
|
90
90
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
const _ = require('lodash');
|
|
2
1
|
const validator = require('@tryghost/validator');
|
|
3
2
|
const ObjectId = require('bson-objectid').default;
|
|
4
3
|
const ghostBookshelf = require('./base');
|
|
@@ -11,9 +10,9 @@ const {pipeline} = require('@tryghost/promise');
|
|
|
11
10
|
const validatePassword = require('../lib/validate-password');
|
|
12
11
|
const permissions = require('../services/permissions');
|
|
13
12
|
const urlUtils = require('../../shared/url-utils');
|
|
14
|
-
const activeStates = ['active', 'warn-1', 'warn-2', 'warn-3', 'warn-4'];
|
|
15
|
-
const ASSIGNABLE_ROLES = ['Administrator', 'Editor', 'Author', 'Contributor'];
|
|
16
13
|
const {setIsRoles} = require('./role-utils');
|
|
14
|
+
const activeStates = ['active', 'warn-1', 'warn-2', 'warn-3', 'warn-4'];
|
|
15
|
+
const ASSIGNABLE_ROLES = ['Administrator', 'Super Editor', 'Editor', 'Author', 'Contributor'];
|
|
17
16
|
|
|
18
17
|
const messages = {
|
|
19
18
|
valueCannotBeBlank: 'Value in [{tableName}.{columnKey}] cannot be blank.',
|
|
@@ -77,7 +76,7 @@ User = ghostBookshelf.Model.extend({
|
|
|
77
76
|
},
|
|
78
77
|
|
|
79
78
|
format(options) {
|
|
80
|
-
if (
|
|
79
|
+
if (options.website &&
|
|
81
80
|
!validator.isURL(options.website, {
|
|
82
81
|
require_protocol: true,
|
|
83
82
|
protocols: ['http', 'https']
|
|
@@ -130,7 +129,7 @@ User = ghostBookshelf.Model.extend({
|
|
|
130
129
|
onDestroyed: function onDestroyed(model, options) {
|
|
131
130
|
ghostBookshelf.Model.prototype.onDestroyed.apply(this, arguments);
|
|
132
131
|
|
|
133
|
-
if (
|
|
132
|
+
if (activeStates.includes(model.previous('status'))) {
|
|
134
133
|
model.emitChange('deactivated', options);
|
|
135
134
|
}
|
|
136
135
|
|
|
@@ -143,7 +142,7 @@ User = ghostBookshelf.Model.extend({
|
|
|
143
142
|
model.emitChange('added', options);
|
|
144
143
|
|
|
145
144
|
// active is the default state, so if status isn't provided, this will be an active user
|
|
146
|
-
if (!model.get('status') ||
|
|
145
|
+
if (!model.get('status') || activeStates.includes(model.get('status'))) {
|
|
147
146
|
model.emitChange('activated', options);
|
|
148
147
|
}
|
|
149
148
|
},
|
|
@@ -152,7 +151,7 @@ User = ghostBookshelf.Model.extend({
|
|
|
152
151
|
ghostBookshelf.Model.prototype.onUpdated.apply(this, arguments);
|
|
153
152
|
|
|
154
153
|
model.statusChanging = model.get('status') !== model.previous('status');
|
|
155
|
-
model.isActive =
|
|
154
|
+
model.isActive = activeStates.includes(model.get('status'));
|
|
156
155
|
|
|
157
156
|
if (model.statusChanging) {
|
|
158
157
|
model.emitChange(model.isActive ? 'activated' : 'deactivated', options);
|
|
@@ -456,18 +455,16 @@ User = ghostBookshelf.Model.extend({
|
|
|
456
455
|
const options = this.filterOptions(unfilteredOptions, 'findOne');
|
|
457
456
|
let query;
|
|
458
457
|
let status;
|
|
459
|
-
let data =
|
|
458
|
+
let data = JSON.parse(JSON.stringify(dataToClone));
|
|
460
459
|
const lookupRole = data.role;
|
|
461
460
|
|
|
462
461
|
// Ensure only valid fields/columns are added to query
|
|
463
462
|
if (options.columns) {
|
|
464
|
-
options.columns =
|
|
463
|
+
options.columns = options.columns.filter(col => this.prototype.permittedAttributes().includes(col));
|
|
465
464
|
}
|
|
466
465
|
|
|
467
466
|
delete data.role;
|
|
468
|
-
data =
|
|
469
|
-
status: 'all'
|
|
470
|
-
});
|
|
467
|
+
data = Object.assign({}, {status: 'all'}, data || {});
|
|
471
468
|
|
|
472
469
|
status = data.status;
|
|
473
470
|
delete data.status;
|
|
@@ -476,7 +473,7 @@ User = ghostBookshelf.Model.extend({
|
|
|
476
473
|
|
|
477
474
|
// Support finding by role
|
|
478
475
|
if (lookupRole) {
|
|
479
|
-
options.withRelated =
|
|
476
|
+
options.withRelated = [...new Set([...(options.withRelated || []), 'roles'])];
|
|
480
477
|
query = this.forge(data);
|
|
481
478
|
|
|
482
479
|
query.query('join', 'roles_users', 'users.id', '=', 'roles_users.user_id');
|
|
@@ -520,7 +517,7 @@ User = ghostBookshelf.Model.extend({
|
|
|
520
517
|
} else if (type === 'recommendation-received') {
|
|
521
518
|
filter += '+recommendation_notifications:true';
|
|
522
519
|
}
|
|
523
|
-
const updatedOptions =
|
|
520
|
+
const updatedOptions = Object.assign({}, options, {filter, withRelated: ['roles']});
|
|
524
521
|
return this.findAll(updatedOptions).then((users) => {
|
|
525
522
|
return users.toJSON().filter((user) => {
|
|
526
523
|
return user?.roles?.some((role) => {
|
|
@@ -641,7 +638,7 @@ User = ghostBookshelf.Model.extend({
|
|
|
641
638
|
add: function add(dataToClone, unfilteredOptions) {
|
|
642
639
|
const options = this.filterOptions(unfilteredOptions, 'add');
|
|
643
640
|
const self = this;
|
|
644
|
-
const data =
|
|
641
|
+
const data = JSON.parse(JSON.stringify(dataToClone));
|
|
645
642
|
let userData = this.filterData(data);
|
|
646
643
|
let roles;
|
|
647
644
|
|
|
@@ -653,7 +650,7 @@ User = ghostBookshelf.Model.extend({
|
|
|
653
650
|
}
|
|
654
651
|
|
|
655
652
|
function getAuthorRole() {
|
|
656
|
-
return ghostBookshelf.model('Role').findOne({name: 'Author'},
|
|
653
|
+
return ghostBookshelf.model('Role').findOne({name: 'Author'}, {transacting: options.transacting})
|
|
657
654
|
.then(function then(authorRole) {
|
|
658
655
|
return [authorRole.get('id')];
|
|
659
656
|
});
|
|
@@ -684,7 +681,7 @@ User = ghostBookshelf.Model.extend({
|
|
|
684
681
|
roles = _roles;
|
|
685
682
|
|
|
686
683
|
// CASE: it is possible to add roles by name, by id or by object
|
|
687
|
-
if (
|
|
684
|
+
if (typeof roles[0] === 'string' && !ObjectId.isValid(roles[0])) {
|
|
688
685
|
const rolePromises = roles.map((roleName) => {
|
|
689
686
|
return ghostBookshelf.model('Role').findOne({
|
|
690
687
|
name: roleName
|
|
@@ -781,6 +778,23 @@ User = ghostBookshelf.Model.extend({
|
|
|
781
778
|
});
|
|
782
779
|
},
|
|
783
780
|
|
|
781
|
+
/**
|
|
782
|
+
* Checks if a user has permission to perform an action on another user
|
|
783
|
+
*
|
|
784
|
+
* @param {Object|string|number} userModelOrId - The user model or ID being acted upon
|
|
785
|
+
* @param {'edit'|'destroy'} action - The action being performed:
|
|
786
|
+
* - 'edit': Edit user details, status, or role
|
|
787
|
+
* - 'destroy': Delete a user (Owner cannot be deleted)
|
|
788
|
+
* @param {Object} context - The context of the request, containing the current user's ID
|
|
789
|
+
* @param {Object} unsafeAttrs - The attributes being modified in the action
|
|
790
|
+
* @param {Object} loadedPermissions - The permissions of the user making the request
|
|
791
|
+
* @param {boolean} hasUserPermission - Whether the user has permission based on user roles
|
|
792
|
+
* @param {boolean} hasApiKeyPermission - Whether the user has permission based on API key
|
|
793
|
+
* @returns {Promise<boolean>} Resolves if the action is permitted, rejects with NoPermissionError if not
|
|
794
|
+
* @throws {errors.NotFoundError} When the target user is not found
|
|
795
|
+
* @throws {errors.NoPermissionError} When the action is not permitted
|
|
796
|
+
* @throws {errors.ValidationError} When role changes are invalid
|
|
797
|
+
*/
|
|
784
798
|
permissible: async function permissible(userModelOrId, action, context, unsafeAttrs, loadedPermissions, hasUserPermission, hasApiKeyPermission) {
|
|
785
799
|
const self = this;
|
|
786
800
|
const userModel = userModelOrId;
|
|
@@ -788,13 +802,13 @@ User = ghostBookshelf.Model.extend({
|
|
|
788
802
|
const {isOwner, isEitherEditor} = setIsRoles(loadedPermissions);
|
|
789
803
|
|
|
790
804
|
// If we passed in a model without its related roles, we need to fetch it again
|
|
791
|
-
if (
|
|
805
|
+
if (typeof userModelOrId === 'object' && !(typeof userModelOrId.related('roles') === 'object')) {
|
|
792
806
|
userModelOrId = userModelOrId.id;
|
|
793
807
|
}
|
|
794
808
|
// If we passed in an id instead of a model get the model first
|
|
795
|
-
if (
|
|
809
|
+
if (typeof userModelOrId === 'number' || typeof userModelOrId === 'string') {
|
|
796
810
|
// Grab the original args without the first one
|
|
797
|
-
origArgs =
|
|
811
|
+
origArgs = Array.from(arguments).slice(1);
|
|
798
812
|
|
|
799
813
|
// Get the actual user model
|
|
800
814
|
return this.findOne({
|
|
@@ -822,15 +836,12 @@ User = ghostBookshelf.Model.extend({
|
|
|
822
836
|
}
|
|
823
837
|
|
|
824
838
|
if (action === 'edit') {
|
|
825
|
-
// Users with the role 'Editor', 'Author', and 'Contributor' have complex permissions when the action === 'edit'
|
|
826
|
-
// We now have all the info we need to construct the permissions
|
|
827
|
-
|
|
828
839
|
if (context.user === userModel.get('id')) {
|
|
829
840
|
// If this is the same user that requests the operation allow it.
|
|
830
841
|
hasUserPermission = true;
|
|
831
|
-
} else if (
|
|
842
|
+
} else if (loadedPermissions.user && userModel.hasRole('Owner')) {
|
|
832
843
|
// Owner can only be edited by owner
|
|
833
|
-
hasUserPermission =
|
|
844
|
+
hasUserPermission = isOwner;
|
|
834
845
|
} else if (isEitherEditor) {
|
|
835
846
|
// If the user we are trying to edit is an Author or Contributor, allow it
|
|
836
847
|
hasUserPermission = userModel.hasRole('Author') || userModel.hasRole('Contributor');
|
|
@@ -1058,7 +1069,7 @@ User = ghostBookshelf.Model.extend({
|
|
|
1058
1069
|
|
|
1059
1070
|
// check if user has the owner role
|
|
1060
1071
|
const currentRoles = contextUser.toJSON(options).roles;
|
|
1061
|
-
if (!
|
|
1072
|
+
if (!currentRoles.some(role => role.id === ownerRole.id)) {
|
|
1062
1073
|
return Promise.reject(new errors.NoPermissionError({
|
|
1063
1074
|
message: tpl(messages.onlyOwnerCanTransferOwnerRole)
|
|
1064
1075
|
}));
|
|
@@ -1081,7 +1092,7 @@ User = ghostBookshelf.Model.extend({
|
|
|
1081
1092
|
|
|
1082
1093
|
const {roles: currentRoles, status} = user.toJSON(options);
|
|
1083
1094
|
|
|
1084
|
-
if (!
|
|
1095
|
+
if (!currentRoles.some(role => role.id === adminRole.id)) {
|
|
1085
1096
|
return Promise.reject(new errors.ValidationError({
|
|
1086
1097
|
message: tpl(messages.onlyAdmCanBeAssignedOwnerRole)
|
|
1087
1098
|
}));
|
|
@@ -1139,4 +1150,4 @@ Users = ghostBookshelf.Collection.extend({
|
|
|
1139
1150
|
module.exports = {
|
|
1140
1151
|
User: ghostBookshelf.model('User', User),
|
|
1141
1152
|
Users: ghostBookshelf.collection('Users', Users)
|
|
1142
|
-
};
|
|
1153
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const queries = require('../../lib/queries');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Updates email analytics for a specific member
|
|
5
|
+
*
|
|
6
|
+
* @param {Object} options - The options object
|
|
7
|
+
* @param {string} options.memberId - The ID of the member to update analytics for
|
|
8
|
+
* @returns {Promise<Object>} The result of the aggregation query (1/0)
|
|
9
|
+
*/
|
|
10
|
+
module.exports = async function updateMemberEmailAnalytics({memberId}) {
|
|
11
|
+
const result = await queries.aggregateMemberStats(memberId);
|
|
12
|
+
return result;
|
|
13
|
+
};
|