feat: filter query preset constraints (#12485)
You can now specify exactly who can change the constraints within a
query preset.
For example, you want to ensure that only "admins" are allowed to set a
preset to "everyone".
To do this, you can use the new `queryPresets.filterConstraints`
property. When a user lacks the permission to change a constraint, the
option will either be hidden from them or disabled if it is already set.
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
queryPresets: {
// ...
filterConstraints: ({ req, options }) =>
!req.user?.roles?.includes('admin')
? options.filter(
(option) =>
(typeof option === 'string' ? option : option.value) !==
'everyone',
)
: options,
},
})
```
The `filterConstraints` functions takes the same arguments as
`reduceOptions` property on select fields introduced in #12487.
This commit is contained in:
@@ -11,6 +11,7 @@ import { seed } from './seed.js'
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
// eslint-disable-next-line no-restricted-exports
|
||||
export default buildConfigWithDefaults({
|
||||
admin: {
|
||||
importMap: {
|
||||
@@ -26,6 +27,12 @@ export default buildConfigWithDefaults({
|
||||
read: ({ req: { user } }) => Boolean(user?.roles?.length && !user?.roles?.includes('user')),
|
||||
update: ({ req: { user } }) => Boolean(user?.roles?.length && !user?.roles?.includes('user')),
|
||||
},
|
||||
filterConstraints: ({ req, options }) =>
|
||||
!req.user?.roles?.includes('admin')
|
||||
? options.filter(
|
||||
(option) => (typeof option === 'string' ? option : option.value) !== 'onlyAdmins',
|
||||
)
|
||||
: options,
|
||||
constraints: {
|
||||
read: [
|
||||
{
|
||||
@@ -43,6 +50,11 @@ export default buildConfigWithDefaults({
|
||||
value: 'noone',
|
||||
access: () => false,
|
||||
},
|
||||
{
|
||||
label: 'Only Admins',
|
||||
value: 'onlyAdmins',
|
||||
access: ({ req: { user } }) => Boolean(user?.roles?.includes('admin')),
|
||||
},
|
||||
],
|
||||
update: [
|
||||
{
|
||||
@@ -55,6 +67,11 @@ export default buildConfigWithDefaults({
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: 'Only Admins',
|
||||
value: 'onlyAdmins',
|
||||
access: ({ req: { user } }) => Boolean(user?.roles?.includes('admin')),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -545,6 +545,89 @@ describe('Query Presets', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('should only allow admins to select the "onlyAdmins" preset (via `filterOptions`)', async () => {
|
||||
try {
|
||||
const presetForAdminsCreatedByEditor = await payload.create({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
user: editorUser,
|
||||
overrideAccess: false,
|
||||
data: {
|
||||
title: 'Admins (Created by Editor)',
|
||||
where: {
|
||||
text: {
|
||||
equals: 'example page',
|
||||
},
|
||||
},
|
||||
access: {
|
||||
read: {
|
||||
constraint: 'onlyAdmins',
|
||||
},
|
||||
update: {
|
||||
constraint: 'onlyAdmins',
|
||||
},
|
||||
},
|
||||
relatedCollection: 'pages',
|
||||
},
|
||||
})
|
||||
|
||||
expect(presetForAdminsCreatedByEditor).toBeFalsy()
|
||||
} catch (error: unknown) {
|
||||
expect((error as Error).message).toBe(
|
||||
'The following fields are invalid: Sharing settings > Read > Specify who can read this Preset, Sharing settings > Update > Specify who can update this Preset',
|
||||
)
|
||||
}
|
||||
|
||||
const presetForAdminsCreatedByAdmin = await payload.create({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
data: {
|
||||
title: 'Admins (Created by Admin)',
|
||||
where: {
|
||||
text: {
|
||||
equals: 'example page',
|
||||
},
|
||||
},
|
||||
access: {
|
||||
read: {
|
||||
constraint: 'onlyAdmins',
|
||||
},
|
||||
update: {
|
||||
constraint: 'onlyAdmins',
|
||||
},
|
||||
},
|
||||
relatedCollection: 'pages',
|
||||
},
|
||||
})
|
||||
|
||||
expect(presetForAdminsCreatedByAdmin).toBeDefined()
|
||||
|
||||
// attempt to update the preset using an editor user
|
||||
try {
|
||||
const presetUpdatedByEditorUser = await payload.update({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
id: presetForAdminsCreatedByAdmin.id,
|
||||
user: editorUser,
|
||||
overrideAccess: false,
|
||||
data: {
|
||||
title: 'From `onlyAdmins` to `onlyMe` (Updated by Editor)',
|
||||
access: {
|
||||
read: {
|
||||
constraint: 'onlyMe',
|
||||
},
|
||||
update: {
|
||||
constraint: 'onlyMe',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(presetUpdatedByEditorUser).toBeFalsy()
|
||||
} catch (error: unknown) {
|
||||
expect((error as Error).message).toBe('You are not allowed to perform this action.')
|
||||
}
|
||||
})
|
||||
|
||||
it('should respect access when set to "specificRoles"', async () => {
|
||||
const presetForSpecificRoles = await payload.create({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
|
||||
@@ -227,12 +227,12 @@ export interface PayloadQueryPreset {
|
||||
isShared?: boolean | null;
|
||||
access?: {
|
||||
read?: {
|
||||
constraint?: ('everyone' | 'onlyMe' | 'specificUsers' | 'specificRoles' | 'noone') | null;
|
||||
constraint?: ('everyone' | 'onlyMe' | 'specificUsers' | 'specificRoles' | 'noone' | 'onlyAdmins') | null;
|
||||
users?: (string | User)[] | null;
|
||||
roles?: ('admin' | 'editor' | 'user')[] | null;
|
||||
};
|
||||
update?: {
|
||||
constraint?: ('everyone' | 'onlyMe' | 'specificUsers' | 'specificRoles') | null;
|
||||
constraint?: ('everyone' | 'onlyMe' | 'specificUsers' | 'specificRoles' | 'onlyAdmins') | null;
|
||||
users?: (string | User)[] | null;
|
||||
roles?: ('admin' | 'editor' | 'user')[] | null;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user