apostrophe 4.28.0 → 4.29.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/CHANGELOG.md +33 -3
- package/README.md +142 -0
- package/defaults.js +1 -0
- package/lib/safe-json-script.js +27 -0
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBarLocale.vue +1 -1
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +1 -0
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaWidget.vue +3 -5
- package/modules/@apostrophecms/area/ui/apos/components/AposBreadcrumbOperations.vue +13 -1
- package/modules/@apostrophecms/asset/lib/globalIcons.js +3 -0
- package/modules/@apostrophecms/attachment/index.js +43 -1
- package/modules/@apostrophecms/color-field/index.js +7 -1
- package/modules/@apostrophecms/doc/index.js +11 -1
- package/modules/@apostrophecms/doc-type/index.js +165 -32
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +1 -1
- package/modules/@apostrophecms/doc-type/ui/apos/logic/AposDocContextMenu.js +104 -59
- package/modules/@apostrophecms/file/index.js +109 -9
- package/modules/@apostrophecms/i18n/i18n/de.json +0 -2
- package/modules/@apostrophecms/i18n/i18n/en.json +40 -1
- package/modules/@apostrophecms/i18n/i18n/es.json +0 -1
- package/modules/@apostrophecms/i18n/i18n/fr.json +0 -1
- package/modules/@apostrophecms/i18n/i18n/it.json +0 -1
- package/modules/@apostrophecms/i18n/i18n/pt-BR.json +0 -1
- package/modules/@apostrophecms/i18n/i18n/sk.json +0 -1
- package/modules/@apostrophecms/i18n/ui/apos/apps/AposI18nBatchReporting.js +18 -1
- package/modules/@apostrophecms/i18n/ui/apos/apps/AposI18nLocalizeActions.js +50 -0
- package/modules/@apostrophecms/i18n/ui/apos/components/AposI18nLocalize.vue +56 -13
- package/modules/@apostrophecms/image/ui/apos/components/AposImageRelationshipEditor.vue +8 -2
- package/modules/@apostrophecms/layout-column-widget/index.js +156 -163
- package/modules/@apostrophecms/layout-widget/index.js +7 -2
- package/modules/@apostrophecms/layout-widget/ui/apos/components/AposAreaLayoutEditor.vue +6 -11
- package/modules/@apostrophecms/layout-widget/ui/apos/components/AposGridColumn.vue +3 -5
- package/modules/@apostrophecms/layout-widget/ui/apos/components/AposGridLayout.vue +4 -4
- package/modules/@apostrophecms/layout-widget/ui/apos/components/AposGridManager.vue +0 -16
- package/modules/@apostrophecms/layout-widget/ui/apos/lib/grid-state.mjs +7 -27
- package/modules/@apostrophecms/layout-widget/views/column.html +7 -9
- package/modules/@apostrophecms/login/index.js +39 -40
- package/modules/@apostrophecms/modal/ui/apos/components/AposDocsManagerToolbar.vue +17 -2
- package/modules/@apostrophecms/modal/ui/apos/components/AposModal.vue +3 -2
- package/modules/@apostrophecms/notification/ui/apos/components/AposNotification.vue +1 -0
- package/modules/@apostrophecms/page/index.js +2 -0
- package/modules/@apostrophecms/piece-type/index.js +3 -1
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +1 -0
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerDisplay.vue +5 -0
- package/modules/@apostrophecms/recently-edited/index.js +831 -0
- package/modules/@apostrophecms/recently-edited/ui/apos/components/AposCellTitle.vue +54 -0
- package/modules/@apostrophecms/recently-edited/ui/apos/components/AposRecentlyEditedCombo.vue +454 -0
- package/modules/@apostrophecms/recently-edited/ui/apos/components/AposRecentlyEditedFilterTag.vue +75 -0
- package/modules/@apostrophecms/recently-edited/ui/apos/components/AposRecentlyEditedFilters.vue +287 -0
- package/modules/@apostrophecms/recently-edited/ui/apos/components/AposRecentlyEditedIcon.vue +16 -0
- package/modules/@apostrophecms/recently-edited/ui/apos/components/AposRecentlyEditedManager.vue +346 -0
- package/modules/@apostrophecms/recently-edited/ui/apos/composables/useRecentlyEditedBatch.js +193 -0
- package/modules/@apostrophecms/recently-edited/ui/apos/composables/useRecentlyEditedData.js +276 -0
- package/modules/@apostrophecms/recently-edited/ui/apos/composables/useRecentlyEditedFetch.js +199 -0
- package/modules/@apostrophecms/recently-edited/ui/apos/composables/useRecentlyEditedFilters.js +100 -0
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputRelationship.js +8 -4
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputWrapper.js +1 -1
- package/modules/@apostrophecms/styles/index.js +10 -0
- package/modules/@apostrophecms/styles/lib/apiRoutes.js +6 -0
- package/modules/@apostrophecms/styles/lib/handlers.js +5 -0
- package/modules/@apostrophecms/styles/lib/methods.js +9 -3
- package/modules/@apostrophecms/styles/lib/presets.js +119 -0
- package/modules/@apostrophecms/styles/ui/apos/components/TheAposStyles.vue +3 -8
- package/modules/@apostrophecms/styles/ui/apos/composables/AposStyles.js +1 -3
- package/modules/@apostrophecms/styles/ui/apos/render-factory.js +29 -0
- package/modules/@apostrophecms/styles/ui/apos/universal/backgroundHelpers.mjs +140 -0
- package/modules/@apostrophecms/styles/ui/apos/universal/customRules.mjs +105 -0
- package/modules/@apostrophecms/styles/ui/apos/universal/render.mjs +195 -15
- package/modules/@apostrophecms/template/index.js +22 -6
- package/modules/@apostrophecms/ui/ui/apos/components/AposCellContextMenu.vue +2 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenu.vue +18 -4
- package/modules/@apostrophecms/ui/ui/apos/composables/useInfiniteScroll.js +91 -0
- package/modules/@apostrophecms/ui/ui/apos/scss/global/_theme.scss +1 -0
- package/modules/@apostrophecms/ui/ui/apos/stores/modal.js +5 -2
- package/modules/@apostrophecms/ui/ui/apos/utils/index.js +9 -0
- package/modules/@apostrophecms/url/index.js +38 -4
- package/modules/@apostrophecms/widget-type/index.js +22 -6
- package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidgetEditor.vue +8 -4
- package/package.json +19 -19
- package/test/files.js +129 -0
- package/test/layout-widget-migration.js +719 -0
- package/test/login-requirements.js +1 -1
- package/test/pieces-public-api.js +80 -0
- package/test/pieces.js +25 -0
- package/test/recently-edited.js +2311 -0
- package/test/schemas.js +39 -3
- package/test/static-build.js +642 -0
- package/test/styles.js +2569 -0
- package/modules/@apostrophecms/layout-widget/ui/apos/components/AposLayoutColControlDialog.vue +0 -171
|
@@ -118,6 +118,86 @@ describe('Pieces Public API', function() {
|
|
|
118
118
|
assert(!response.results[0].foo);
|
|
119
119
|
});
|
|
120
120
|
|
|
121
|
+
it('should not leak distinct values of non-projected fields via ?choices=', async function() {
|
|
122
|
+
apos.thing.options.publicApiProjection = {
|
|
123
|
+
title: 1,
|
|
124
|
+
_url: 1
|
|
125
|
+
};
|
|
126
|
+
const response = await apos.http.get('/api/v1/thing?choices=title,foo');
|
|
127
|
+
assert(response);
|
|
128
|
+
assert(response.choices);
|
|
129
|
+
// title IS in the public API projection, so its choices are allowed
|
|
130
|
+
assert(response.choices.title);
|
|
131
|
+
assert(response.choices.title.some(c => c.label === 'hello'));
|
|
132
|
+
// foo is NOT in the public API projection: its distinct values
|
|
133
|
+
// must not be exposed to anonymous callers
|
|
134
|
+
assert(
|
|
135
|
+
!response.choices.foo || response.choices.foo.length === 0,
|
|
136
|
+
'choices for non-projected field "foo" must not be exposed publicly'
|
|
137
|
+
);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should not leak distinct values of non-projected fields via ?counts=', async function() {
|
|
141
|
+
apos.thing.options.publicApiProjection = {
|
|
142
|
+
title: 1,
|
|
143
|
+
_url: 1
|
|
144
|
+
};
|
|
145
|
+
const response = await apos.http.get('/api/v1/thing?counts=title,foo');
|
|
146
|
+
assert(response);
|
|
147
|
+
assert(response.counts);
|
|
148
|
+
assert(response.counts.title);
|
|
149
|
+
assert(
|
|
150
|
+
!response.counts.foo || response.counts.foo.length === 0,
|
|
151
|
+
'counts for non-projected field "foo" must not be exposed publicly'
|
|
152
|
+
);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should not leak non-projected fields via dot-notated ?choices= filter names', async function() {
|
|
156
|
+
apos.thing.options.publicApiProjection = {
|
|
157
|
+
title: 1,
|
|
158
|
+
_url: 1
|
|
159
|
+
};
|
|
160
|
+
// A dot-notated filter whose top-level segment is not projected
|
|
161
|
+
// must be blocked the same way the plain field name is.
|
|
162
|
+
const response = await apos.http.get('/api/v1/thing?choices=foo.bar');
|
|
163
|
+
assert(response);
|
|
164
|
+
assert(
|
|
165
|
+
!response.choices || !response.choices['foo.bar'] || response.choices['foo.bar'].length === 0,
|
|
166
|
+
'choices for dot-notated non-projected field "foo.bar" must not be exposed publicly'
|
|
167
|
+
);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should still expose choices for non-projected fields to authenticated API users', async function() {
|
|
171
|
+
apos.thing.options.publicApiProjection = {
|
|
172
|
+
title: 1,
|
|
173
|
+
_url: 1
|
|
174
|
+
};
|
|
175
|
+
const req = apos.task.getReq();
|
|
176
|
+
// Simulate a full-API caller: canAccessApi is true, no projection applied
|
|
177
|
+
const query = apos.thing.find(req).choices([ 'foo' ]);
|
|
178
|
+
await query.toArray();
|
|
179
|
+
const choices = query.get('choicesResults');
|
|
180
|
+
assert(choices);
|
|
181
|
+
assert(choices.foo);
|
|
182
|
+
assert(choices.foo.some(c => c.value === 'bar'));
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('should not restrict choices when an authenticated user voluntarily sets a projection', async function() {
|
|
186
|
+
apos.thing.options.publicApiProjection = {
|
|
187
|
+
title: 1,
|
|
188
|
+
_url: 1
|
|
189
|
+
};
|
|
190
|
+
const req = apos.task.getReq();
|
|
191
|
+
// Authenticated user narrows their own query via project() — this is
|
|
192
|
+
// voluntary and must not block choices for fields outside that projection
|
|
193
|
+
const query = apos.thing.find(req).project({ title: 1 }).choices([ 'foo' ]);
|
|
194
|
+
await query.toArray();
|
|
195
|
+
const choices = query.get('choicesResults');
|
|
196
|
+
assert(choices);
|
|
197
|
+
assert(choices.foo);
|
|
198
|
+
assert(choices.foo.some(c => c.value === 'bar'));
|
|
199
|
+
});
|
|
200
|
+
|
|
121
201
|
it('should not set a "max-age" cache-control value when retrieving pieces, when cache option is not set, with a public API projection', async function() {
|
|
122
202
|
apos.thing.options.publicApiProjection = {
|
|
123
203
|
title: 1,
|
package/test/pieces.js
CHANGED
|
@@ -2364,6 +2364,31 @@ describe('Pieces', function() {
|
|
|
2364
2364
|
assert.deepEqual(actual, expected);
|
|
2365
2365
|
});
|
|
2366
2366
|
|
|
2367
|
+
it('should not leak viewPermission-protected fields via ?choices=', async function() {
|
|
2368
|
+
const jar = await t.loginAs(apos, 'editor');
|
|
2369
|
+
|
|
2370
|
+
const req = apos.task.getReq();
|
|
2371
|
+
const candidate = {
|
|
2372
|
+
...apos.modules.board.newInstance(),
|
|
2373
|
+
title: 'Icarus',
|
|
2374
|
+
slug: 'icarus',
|
|
2375
|
+
discontinued: 'April 2077'
|
|
2376
|
+
};
|
|
2377
|
+
await apos.modules.board.insert(req, candidate);
|
|
2378
|
+
|
|
2379
|
+
// Editor does not have 'publish' permission on 'board', so
|
|
2380
|
+
// 'discontinued' (viewPermission: { action: 'publish', type: 'board' })
|
|
2381
|
+
// must not appear in choices
|
|
2382
|
+
const response = await apos.http.get('/api/v1/board?choices=title,discontinued', { jar });
|
|
2383
|
+
assert(response);
|
|
2384
|
+
assert(response.choices);
|
|
2385
|
+
assert(response.choices.title);
|
|
2386
|
+
assert(
|
|
2387
|
+
!response.choices.discontinued || response.choices.discontinued.length === 0,
|
|
2388
|
+
'choices for viewPermission-protected field "discontinued" must not be exposed to editors'
|
|
2389
|
+
);
|
|
2390
|
+
});
|
|
2391
|
+
|
|
2367
2392
|
it('should be able to edit fields with editPermission when having appropriate credentials on rest API', async function() {
|
|
2368
2393
|
const jar = await t.loginAs(apos, 'editor');
|
|
2369
2394
|
|