feat: prevent query preset lockout (#12322)
Prevents an accidental lockout of query preset documents. An "accidental lockout" occurs when the user sets access control on a preset and excludes themselves. This can happen in a variety of scenarios, including: - You select `specificUsers` without specifying yourself - You select `specificRoles` without specifying a role that you are a part of - Etc. #### How it works To make this happen, we use a custom validation function that executes access against the user's proposed changes. If those changes happen to remove access for them, we throw a validation error and prevent that change from ever taking place. This means that only a user with proper access can remove another user from the preset. You cannot remove yourself. To do this, we create a temporary record in the database that we can query against. We use transactions to ensure that the temporary record is not persisted once our work is completed. Since not all Payload projects have transactions enabled, we flag these temporary records with the `isTemp` field. Once created, we query the temp document to determine its permissions. If any of the operations throw an error, this means the user can no longer act on them, and we throw a validation error. #### Alternative Approach A previous approach that was explored was to add an `owner` field to the presets collection. This way, the "owner" of the preset would be able to completely bypass all access control, effectively eliminating the possibility of a lockout event. But this doesn't work for other users who may have update access. E.g. they could still accidentally remove themselves from the read or update operation, preventing them from accessing that preset after submitting the form. We need a solution that works for all users, not just the owner.
This commit is contained in:
7
.vscode/launch.json
vendored
7
.vscode/launch.json
vendored
@@ -63,6 +63,13 @@
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
},
|
||||
{
|
||||
"command": "pnpm tsx --no-deprecation test/dev.ts query-presets",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"name": "Run Dev Query Presets",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
},
|
||||
{
|
||||
"command": "pnpm tsx --no-deprecation test/dev.ts login-with-username",
|
||||
"cwd": "${workspaceFolder}",
|
||||
|
||||
@@ -108,7 +108,6 @@ export type BeforeChangeRichTextHookArgs<
|
||||
previousSiblingDoc?: TData
|
||||
/** The previous value of the field, before changes */
|
||||
previousValue?: TValue
|
||||
|
||||
/**
|
||||
* The original siblingData with locales (not modified by any hooks).
|
||||
*/
|
||||
|
||||
@@ -149,6 +149,7 @@ export default async function createLocal<
|
||||
}
|
||||
|
||||
const req = await createLocalReq(options, payload)
|
||||
|
||||
req.file = file ?? (await getFileByPath(filePath))
|
||||
|
||||
return createOperation<TSlug, TSelect>({
|
||||
|
||||
@@ -113,6 +113,16 @@ export const getQueryPresetsConfig = (config: Config): CollectionConfig => ({
|
||||
: [],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'isTemp',
|
||||
type: 'checkbox',
|
||||
admin: {
|
||||
description:
|
||||
"This is a tempoary field used to determine if updating the preset would remove the user's access to it. When `true`, this record will be deleted after running the preset's `validate` function.",
|
||||
disabled: true,
|
||||
hidden: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
hooks: {
|
||||
beforeValidate: [
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { Field } from '../fields/config/types.js'
|
||||
|
||||
import { fieldAffectsData } from '../fields/config/types.js'
|
||||
import { toWords } from '../utilities/formatLabels.js'
|
||||
import { preventLockout } from './preventLockout.js'
|
||||
import { operations, type QueryPresetConstraint } from './types.js'
|
||||
|
||||
export const getConstraints = (config: Config): Field => ({
|
||||
@@ -101,4 +102,5 @@ export const getConstraints = (config: Config): Field => ({
|
||||
label: () => toWords(operation),
|
||||
})),
|
||||
label: 'Sharing settings',
|
||||
validate: preventLockout,
|
||||
})
|
||||
|
||||
92
packages/payload/src/query-presets/preventLockout.ts
Normal file
92
packages/payload/src/query-presets/preventLockout.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import type { Validate } from '../fields/config/types.js'
|
||||
|
||||
import { APIError } from '../errors/APIError.js'
|
||||
import { createLocalReq } from '../utilities/createLocalReq.js'
|
||||
import { initTransaction } from '../utilities/initTransaction.js'
|
||||
import { killTransaction } from '../utilities/killTransaction.js'
|
||||
import { queryPresetsCollectionSlug } from './config.js'
|
||||
|
||||
/**
|
||||
* Prevents "accidental lockouts" where a user makes an update that removes their own access to the preset.
|
||||
* This is effectively an access control function proxied through a `validate` function.
|
||||
* How it works:
|
||||
* 1. Creates a temporary record with the incoming data
|
||||
* 2. Attempts to read and update that record with the incoming user
|
||||
* 3. If either of those fail, throws an error to the user
|
||||
* 4. Once finished, prevents the temp record from persisting to the database
|
||||
*/
|
||||
export const preventLockout: Validate = async (
|
||||
value,
|
||||
{ data, overrideAccess, req: incomingReq },
|
||||
) => {
|
||||
// Use context to ensure an infinite loop doesn't occur
|
||||
if (!incomingReq.context._preventLockout && !overrideAccess) {
|
||||
const req = await createLocalReq(
|
||||
{
|
||||
context: {
|
||||
_preventLockout: true,
|
||||
},
|
||||
req: {
|
||||
user: incomingReq.user,
|
||||
},
|
||||
},
|
||||
incomingReq.payload,
|
||||
)
|
||||
|
||||
// Might be `null` if no transactions are enabled
|
||||
const transaction = await initTransaction(req)
|
||||
|
||||
// create a temp record to validate the constraints, using the req
|
||||
const tempPreset = await req.payload.create({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
data: {
|
||||
...data,
|
||||
isTemp: true,
|
||||
},
|
||||
req,
|
||||
})
|
||||
|
||||
let canUpdate = false
|
||||
let canRead = false
|
||||
|
||||
try {
|
||||
await req.payload.findByID({
|
||||
id: tempPreset.id,
|
||||
collection: queryPresetsCollectionSlug,
|
||||
overrideAccess: false,
|
||||
req,
|
||||
user: req.user,
|
||||
})
|
||||
|
||||
canRead = true
|
||||
|
||||
await req.payload.update({
|
||||
id: tempPreset.id,
|
||||
collection: queryPresetsCollectionSlug,
|
||||
data: tempPreset,
|
||||
overrideAccess: false,
|
||||
req,
|
||||
user: req.user,
|
||||
})
|
||||
|
||||
canUpdate = true
|
||||
} catch (_err) {
|
||||
if (!canRead || !canUpdate) {
|
||||
throw new APIError('Cannot remove yourself from this preset.', 403, {}, true)
|
||||
}
|
||||
} finally {
|
||||
if (transaction) {
|
||||
await killTransaction(req)
|
||||
} else {
|
||||
// delete the temp record
|
||||
await req.payload.delete({
|
||||
id: tempPreset.id,
|
||||
collection: queryPresetsCollectionSlug,
|
||||
req,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true as unknown as true
|
||||
}
|
||||
@@ -78,6 +78,7 @@ export async function getEntityPolicies<T extends Args>(args: T): Promise<Return
|
||||
overrideAccess: true,
|
||||
req,
|
||||
}
|
||||
|
||||
if (operation === 'readVersions') {
|
||||
const paginatedRes = await payload.findVersions({
|
||||
...options,
|
||||
@@ -91,6 +92,7 @@ export async function getEntityPolicies<T extends Args>(args: T): Promise<Return
|
||||
pagination: false,
|
||||
where: combineQueries(where, { id: { equals: id } }),
|
||||
})
|
||||
|
||||
return paginatedRes?.docs?.[0] || undefined
|
||||
}
|
||||
|
||||
@@ -119,6 +121,7 @@ export async function getEntityPolicies<T extends Args>(args: T): Promise<Return
|
||||
docBeingAccessed = doc
|
||||
})
|
||||
}
|
||||
|
||||
// awaiting the promise to ensure docBeingAccessed is assigned before it is used
|
||||
await docBeingAccessed
|
||||
|
||||
|
||||
@@ -68,6 +68,9 @@ export const useQueryPresets = ({
|
||||
const filterOptions = useMemo(
|
||||
() => ({
|
||||
'payload-query-presets': {
|
||||
isTemp: {
|
||||
not_equals: true,
|
||||
},
|
||||
relatedCollection: {
|
||||
equals: collectionSlug,
|
||||
},
|
||||
|
||||
@@ -104,7 +104,7 @@ export const QueryPresetsWhereField: JSONFieldClientComponent = ({
|
||||
{value
|
||||
? transformWhereToNaturalLanguage(
|
||||
value as Where,
|
||||
getTranslation(collectionConfig.labels.plural, i18n),
|
||||
getTranslation(collectionConfig?.labels?.plural, i18n),
|
||||
)
|
||||
: 'No where query'}
|
||||
</div>
|
||||
|
||||
@@ -15,7 +15,4 @@ export const Pages: CollectionConfig = {
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
versions: {
|
||||
drafts: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -15,7 +15,4 @@ export const Posts: CollectionConfig = {
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
versions: {
|
||||
drafts: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -23,10 +23,8 @@ export default buildConfigWithDefaults({
|
||||
// plural: 'Reports',
|
||||
// },
|
||||
access: {
|
||||
read: ({ req: { user } }) =>
|
||||
user ? user && !user?.roles?.some((role) => role === 'anonymous') : false,
|
||||
update: ({ req: { user } }) =>
|
||||
user ? user && !user?.roles?.some((role) => role === 'anonymous') : false,
|
||||
read: ({ req: { user } }) => Boolean(user?.roles?.length && !user?.roles?.includes('user')),
|
||||
update: ({ req: { user } }) => Boolean(user?.roles?.length && !user?.roles?.includes('user')),
|
||||
},
|
||||
constraints: {
|
||||
read: [
|
||||
@@ -60,7 +58,7 @@ export default buildConfigWithDefaults({
|
||||
],
|
||||
},
|
||||
},
|
||||
collections: [Pages, Users, Posts],
|
||||
collections: [Pages, Posts, Users],
|
||||
onInit: async (payload) => {
|
||||
if (process.env.SEED_IN_CONFIG_ONINIT !== 'false') {
|
||||
await seed(payload)
|
||||
|
||||
@@ -8,7 +8,7 @@ import * as path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
import type { PayloadTestSDK } from '../helpers/sdk/index.js'
|
||||
import type { Config } from './payload-types.js'
|
||||
import type { Config, PayloadQueryPreset } from './payload-types.js'
|
||||
|
||||
import {
|
||||
ensureCompilationIsDone,
|
||||
@@ -39,6 +39,13 @@ let serverURL: string
|
||||
let everyoneID: string | undefined
|
||||
let context: BrowserContext
|
||||
let user: any
|
||||
let ownerUser: any
|
||||
|
||||
let seededData: {
|
||||
everyone: PayloadQueryPreset
|
||||
onlyMe: PayloadQueryPreset
|
||||
specificUsers: PayloadQueryPreset
|
||||
}
|
||||
|
||||
describe('Query Presets', () => {
|
||||
beforeAll(async ({ browser }, testInfo) => {
|
||||
@@ -60,6 +67,19 @@ describe('Query Presets', () => {
|
||||
})
|
||||
?.then((res) => res.user) // TODO: this type is wrong
|
||||
|
||||
ownerUser = await payload
|
||||
.find({
|
||||
collection: 'users',
|
||||
where: {
|
||||
name: {
|
||||
equals: 'Owner',
|
||||
},
|
||||
},
|
||||
limit: 1,
|
||||
depth: 0,
|
||||
})
|
||||
?.then((res) => res.docs[0])
|
||||
|
||||
initPageConsoleErrorCatch(page)
|
||||
|
||||
await ensureCompilationIsDone({ page, serverURL })
|
||||
@@ -83,7 +103,7 @@ describe('Query Presets', () => {
|
||||
},
|
||||
})
|
||||
|
||||
const [, everyone] = await Promise.all([
|
||||
const [, everyone, onlyMe, specificUsers] = await Promise.all([
|
||||
payload.delete({
|
||||
collection: 'payload-preferences',
|
||||
where: {
|
||||
@@ -106,18 +126,24 @@ describe('Query Presets', () => {
|
||||
}),
|
||||
payload.create({
|
||||
collection: 'payload-query-presets',
|
||||
data: seedData.everyone,
|
||||
data: seedData.everyone({ ownerUserID: ownerUser?.id || '' }),
|
||||
}),
|
||||
payload.create({
|
||||
collection: 'payload-query-presets',
|
||||
data: seedData.onlyMe,
|
||||
data: seedData.onlyMe({ ownerUserID: ownerUser?.id || '' }),
|
||||
}),
|
||||
payload.create({
|
||||
collection: 'payload-query-presets',
|
||||
data: seedData.specificUsers({ userID: user?.id || '' }),
|
||||
data: seedData.specificUsers({ ownerUserID: ownerUser?.id || '', adminUserID: user.id }),
|
||||
}),
|
||||
])
|
||||
|
||||
seededData = {
|
||||
everyone,
|
||||
onlyMe,
|
||||
specificUsers,
|
||||
}
|
||||
|
||||
everyoneID = everyone.id
|
||||
} catch (error) {
|
||||
console.error('Error in beforeEach:', error)
|
||||
@@ -126,12 +152,12 @@ describe('Query Presets', () => {
|
||||
|
||||
test('should select preset and apply filters', async () => {
|
||||
await page.goto(pagesUrl.list)
|
||||
await selectPreset({ page, presetTitle: seedData.everyone.title })
|
||||
await selectPreset({ page, presetTitle: seededData.everyone.title })
|
||||
|
||||
await assertURLParams({
|
||||
page,
|
||||
columns: seedData.everyone.columns,
|
||||
where: seedData.everyone.where,
|
||||
columns: seededData.everyone.columns,
|
||||
where: seededData.everyone.where,
|
||||
presetID: everyoneID,
|
||||
})
|
||||
|
||||
@@ -140,14 +166,14 @@ describe('Query Presets', () => {
|
||||
|
||||
test('should clear selected preset and reset filters', async () => {
|
||||
await page.goto(pagesUrl.list)
|
||||
await selectPreset({ page, presetTitle: seedData.everyone.title })
|
||||
await selectPreset({ page, presetTitle: seededData.everyone.title })
|
||||
await clearSelectedPreset({ page })
|
||||
expect(true).toBe(true)
|
||||
})
|
||||
|
||||
test('should delete a preset, clear selection, and reset changes', async () => {
|
||||
await page.goto(pagesUrl.list)
|
||||
await selectPreset({ page, presetTitle: seedData.everyone.title })
|
||||
await selectPreset({ page, presetTitle: seededData.everyone.title })
|
||||
await openListMenu({ page })
|
||||
|
||||
await clickListMenuItem({ page, menuItemLabel: 'Delete' })
|
||||
@@ -172,21 +198,21 @@ describe('Query Presets', () => {
|
||||
|
||||
await expect(
|
||||
modal.locator('tbody tr td button', {
|
||||
hasText: exactText(seedData.everyone.title),
|
||||
hasText: exactText(seededData.everyone.title),
|
||||
}),
|
||||
).toBeHidden()
|
||||
})
|
||||
|
||||
test('should save last used preset to preferences and load on initial render', async () => {
|
||||
await page.goto(pagesUrl.list)
|
||||
await selectPreset({ page, presetTitle: seedData.everyone.title })
|
||||
await selectPreset({ page, presetTitle: seededData.everyone.title })
|
||||
|
||||
await page.reload()
|
||||
|
||||
await assertURLParams({
|
||||
page,
|
||||
columns: seedData.everyone.columns,
|
||||
where: seedData.everyone.where,
|
||||
columns: seededData.everyone.columns,
|
||||
where: seededData.everyone.where,
|
||||
// presetID: everyoneID,
|
||||
})
|
||||
|
||||
@@ -209,7 +235,7 @@ describe('Query Presets', () => {
|
||||
}),
|
||||
).toBeHidden()
|
||||
|
||||
await selectPreset({ page, presetTitle: seedData.everyone.title })
|
||||
await selectPreset({ page, presetTitle: seededData.everyone.title })
|
||||
|
||||
await openListMenu({ page })
|
||||
|
||||
@@ -249,7 +275,7 @@ describe('Query Presets', () => {
|
||||
}),
|
||||
).toBeHidden()
|
||||
|
||||
await selectPreset({ page, presetTitle: seedData.onlyMe.title })
|
||||
await selectPreset({ page, presetTitle: seededData.onlyMe.title })
|
||||
|
||||
await toggleColumn(page, { columnLabel: 'ID' })
|
||||
|
||||
@@ -271,7 +297,7 @@ describe('Query Presets', () => {
|
||||
test('should conditionally render "update for everyone" label based on if preset is shared', async () => {
|
||||
await page.goto(pagesUrl.list)
|
||||
|
||||
await selectPreset({ page, presetTitle: seedData.onlyMe.title })
|
||||
await selectPreset({ page, presetTitle: seededData.onlyMe.title })
|
||||
|
||||
await toggleColumn(page, { columnLabel: 'ID' })
|
||||
|
||||
@@ -284,7 +310,7 @@ describe('Query Presets', () => {
|
||||
}),
|
||||
).toBeVisible()
|
||||
|
||||
await selectPreset({ page, presetTitle: seedData.everyone.title })
|
||||
await selectPreset({ page, presetTitle: seededData.everyone.title })
|
||||
|
||||
await toggleColumn(page, { columnLabel: 'ID' })
|
||||
|
||||
@@ -300,7 +326,7 @@ describe('Query Presets', () => {
|
||||
|
||||
test('should reset active changes', async () => {
|
||||
await page.goto(pagesUrl.list)
|
||||
await selectPreset({ page, presetTitle: seedData.everyone.title })
|
||||
await selectPreset({ page, presetTitle: seededData.everyone.title })
|
||||
|
||||
const { columnContainer } = await toggleColumn(page, { columnLabel: 'ID' })
|
||||
|
||||
@@ -318,7 +344,7 @@ describe('Query Presets', () => {
|
||||
test('should only enter modified state when changes are made to an active preset', async () => {
|
||||
await page.goto(pagesUrl.list)
|
||||
await expect(page.locator('.list-controls__modified')).toBeHidden()
|
||||
await selectPreset({ page, presetTitle: seedData.everyone.title })
|
||||
await selectPreset({ page, presetTitle: seededData.everyone.title })
|
||||
await expect(page.locator('.list-controls__modified')).toBeHidden()
|
||||
await toggleColumn(page, { columnLabel: 'ID' })
|
||||
await expect(page.locator('.list-controls__modified')).toBeVisible()
|
||||
@@ -337,14 +363,14 @@ describe('Query Presets', () => {
|
||||
|
||||
await page.goto(pagesUrl.list)
|
||||
|
||||
await selectPreset({ page, presetTitle: seedData.everyone.title })
|
||||
await selectPreset({ page, presetTitle: seededData.everyone.title })
|
||||
await clickListMenuItem({ page, menuItemLabel: 'Edit' })
|
||||
|
||||
const drawer = page.locator('[id^=doc-drawer_payload-query-presets_0_]')
|
||||
const titleValue = drawer.locator('input[name="title"]')
|
||||
await expect(titleValue).toHaveValue(seedData.everyone.title)
|
||||
await expect(titleValue).toHaveValue(seededData.everyone.title)
|
||||
|
||||
const newTitle = `${seedData.everyone.title} (Updated)`
|
||||
const newTitle = `${seededData.everyone.title} (Updated)`
|
||||
await drawer.locator('input[name="title"]').fill(newTitle)
|
||||
|
||||
await saveDocAndAssert(page)
|
||||
@@ -391,9 +417,9 @@ describe('Query Presets', () => {
|
||||
})
|
||||
|
||||
test('only shows query presets related to the underlying collection', async () => {
|
||||
// no results on `users` collection
|
||||
const postsUrl = new AdminUrlUtil(serverURL, 'posts')
|
||||
await page.goto(postsUrl.list)
|
||||
// no results on `posts` collection
|
||||
const postsURL = new AdminUrlUtil(serverURL, 'posts')
|
||||
await page.goto(postsURL.list)
|
||||
const drawer = await openQueryPresetDrawer({ page })
|
||||
await expect(drawer.locator('.table table > tbody > tr')).toHaveCount(0)
|
||||
await expect(drawer.locator('.collection-list__no-results')).toBeVisible()
|
||||
|
||||
@@ -9,13 +9,13 @@ export const roles: Field = {
|
||||
label: 'Admin',
|
||||
value: 'admin',
|
||||
},
|
||||
{
|
||||
label: 'Editor',
|
||||
value: 'editor',
|
||||
},
|
||||
{
|
||||
label: 'User',
|
||||
value: 'user',
|
||||
},
|
||||
{
|
||||
label: 'Anonymous',
|
||||
value: 'anonymous',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { NextRESTClient } from 'helpers/NextRESTClient.js'
|
||||
import type { Payload, User } from 'payload'
|
||||
|
||||
import path from 'path'
|
||||
@@ -10,10 +9,9 @@ import { initPayloadInt } from '../helpers/initPayloadInt.js'
|
||||
const queryPresetsCollectionSlug = 'payload-query-presets'
|
||||
|
||||
let payload: Payload
|
||||
let restClient: NextRESTClient
|
||||
let user: User
|
||||
let user2: User
|
||||
let anonymousUser: User
|
||||
let adminUser: User
|
||||
let editorUser: User
|
||||
let publicUser: User
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
@@ -21,9 +19,9 @@ const dirname = path.dirname(filename)
|
||||
describe('Query Presets', () => {
|
||||
beforeAll(async () => {
|
||||
// @ts-expect-error: initPayloadInt does not have a proper type definition
|
||||
;({ payload, restClient } = await initPayloadInt(dirname))
|
||||
;({ payload } = await initPayloadInt(dirname))
|
||||
|
||||
user = await payload
|
||||
adminUser = await payload
|
||||
.login({
|
||||
collection: 'users',
|
||||
data: {
|
||||
@@ -33,7 +31,7 @@ describe('Query Presets', () => {
|
||||
})
|
||||
?.then((result) => result.user)
|
||||
|
||||
user2 = await payload
|
||||
editorUser = await payload
|
||||
.login({
|
||||
collection: 'users',
|
||||
data: {
|
||||
@@ -43,11 +41,11 @@ describe('Query Presets', () => {
|
||||
})
|
||||
?.then((result) => result.user)
|
||||
|
||||
anonymousUser = await payload
|
||||
publicUser = await payload
|
||||
.login({
|
||||
collection: 'users',
|
||||
data: {
|
||||
email: 'anonymous@email.com',
|
||||
email: 'public@email.com',
|
||||
password: regularUser.password,
|
||||
},
|
||||
})
|
||||
@@ -155,7 +153,8 @@ describe('Query Presets', () => {
|
||||
it('should respect access when set to "specificUsers"', async () => {
|
||||
const presetForSpecificUsers = await payload.create({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
user,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
data: {
|
||||
title: 'Specific Users',
|
||||
where: {
|
||||
@@ -166,11 +165,11 @@ describe('Query Presets', () => {
|
||||
access: {
|
||||
read: {
|
||||
constraint: 'specificUsers',
|
||||
users: [user.id],
|
||||
users: [adminUser.id],
|
||||
},
|
||||
update: {
|
||||
constraint: 'specificUsers',
|
||||
users: [user.id],
|
||||
users: [adminUser.id],
|
||||
},
|
||||
},
|
||||
relatedCollection: 'pages',
|
||||
@@ -180,7 +179,7 @@ describe('Query Presets', () => {
|
||||
const foundPresetWithUser1 = await payload.findByID({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
depth: 0,
|
||||
user,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
id: presetForSpecificUsers.id,
|
||||
})
|
||||
@@ -188,53 +187,53 @@ describe('Query Presets', () => {
|
||||
expect(foundPresetWithUser1.id).toBe(presetForSpecificUsers.id)
|
||||
|
||||
try {
|
||||
const foundPresetWithUser2 = await payload.findByID({
|
||||
const foundPresetWithEditorUser = await payload.findByID({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
depth: 0,
|
||||
user: user2,
|
||||
user: editorUser,
|
||||
overrideAccess: false,
|
||||
id: presetForSpecificUsers.id,
|
||||
})
|
||||
|
||||
expect(foundPresetWithUser2).toBeFalsy()
|
||||
expect(foundPresetWithEditorUser).toBeFalsy()
|
||||
} catch (error: unknown) {
|
||||
expect((error as Error).message).toBe('Not Found')
|
||||
}
|
||||
|
||||
const presetUpdatedByUser1 = await payload.update({
|
||||
const presetUpdatedByAdminUser = await payload.update({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
id: presetForSpecificUsers.id,
|
||||
user,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
data: {
|
||||
title: 'Specific Users (Updated)',
|
||||
},
|
||||
})
|
||||
|
||||
expect(presetUpdatedByUser1.title).toBe('Specific Users (Updated)')
|
||||
expect(presetUpdatedByAdminUser.title).toBe('Specific Users (Updated)')
|
||||
|
||||
try {
|
||||
const presetUpdatedByUser2 = await payload.update({
|
||||
const presetUpdatedByEditorUser = await payload.update({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
id: presetForSpecificUsers.id,
|
||||
user: user2,
|
||||
user: editorUser,
|
||||
overrideAccess: false,
|
||||
data: {
|
||||
title: 'Specific Users (Updated)',
|
||||
},
|
||||
})
|
||||
|
||||
expect(presetUpdatedByUser2).toBeFalsy()
|
||||
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 "onlyMe"', async () => {
|
||||
// create a new doc so that the creating user is the owner
|
||||
const presetForOnlyMe = await payload.create({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
user,
|
||||
overrideAccess: false,
|
||||
user: adminUser,
|
||||
data: {
|
||||
title: 'Only Me',
|
||||
where: {
|
||||
@@ -257,7 +256,7 @@ describe('Query Presets', () => {
|
||||
const foundPresetWithUser1 = await payload.findByID({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
depth: 0,
|
||||
user,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
id: presetForOnlyMe.id,
|
||||
})
|
||||
@@ -265,15 +264,15 @@ describe('Query Presets', () => {
|
||||
expect(foundPresetWithUser1.id).toBe(presetForOnlyMe.id)
|
||||
|
||||
try {
|
||||
const foundPresetWithUser2 = await payload.findByID({
|
||||
const foundPresetWithEditorUser = await payload.findByID({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
depth: 0,
|
||||
user: user2,
|
||||
user: editorUser,
|
||||
overrideAccess: false,
|
||||
id: presetForOnlyMe.id,
|
||||
})
|
||||
|
||||
expect(foundPresetWithUser2).toBeFalsy()
|
||||
expect(foundPresetWithEditorUser).toBeFalsy()
|
||||
} catch (error: unknown) {
|
||||
expect((error as Error).message).toBe('Not Found')
|
||||
}
|
||||
@@ -281,7 +280,7 @@ describe('Query Presets', () => {
|
||||
const presetUpdatedByUser1 = await payload.update({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
id: presetForOnlyMe.id,
|
||||
user,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
data: {
|
||||
title: 'Only Me (Updated)',
|
||||
@@ -291,17 +290,17 @@ describe('Query Presets', () => {
|
||||
expect(presetUpdatedByUser1.title).toBe('Only Me (Updated)')
|
||||
|
||||
try {
|
||||
const presetUpdatedByUser2 = await payload.update({
|
||||
const presetUpdatedByEditorUser = await payload.update({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
id: presetForOnlyMe.id,
|
||||
user: user2,
|
||||
user: editorUser,
|
||||
overrideAccess: false,
|
||||
data: {
|
||||
title: 'Only Me (Updated)',
|
||||
},
|
||||
})
|
||||
|
||||
expect(presetUpdatedByUser2).toBeFalsy()
|
||||
expect(presetUpdatedByEditorUser).toBeFalsy()
|
||||
} catch (error: unknown) {
|
||||
expect((error as Error).message).toBe('You are not allowed to perform this action.')
|
||||
}
|
||||
@@ -310,7 +309,8 @@ describe('Query Presets', () => {
|
||||
it('should respect access when set to "everyone"', async () => {
|
||||
const presetForEveryone = await payload.create({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
user,
|
||||
overrideAccess: false,
|
||||
user: adminUser,
|
||||
data: {
|
||||
title: 'Everyone',
|
||||
where: {
|
||||
@@ -336,27 +336,27 @@ describe('Query Presets', () => {
|
||||
const foundPresetWithUser1 = await payload.findByID({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
depth: 0,
|
||||
user,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
id: presetForEveryone.id,
|
||||
})
|
||||
|
||||
expect(foundPresetWithUser1.id).toBe(presetForEveryone.id)
|
||||
|
||||
const foundPresetWithUser2 = await payload.findByID({
|
||||
const foundPresetWithEditorUser = await payload.findByID({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
depth: 0,
|
||||
user: user2,
|
||||
user: editorUser,
|
||||
overrideAccess: false,
|
||||
id: presetForEveryone.id,
|
||||
})
|
||||
|
||||
expect(foundPresetWithUser2.id).toBe(presetForEveryone.id)
|
||||
expect(foundPresetWithEditorUser.id).toBe(presetForEveryone.id)
|
||||
|
||||
const presetUpdatedByUser1 = await payload.update({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
id: presetForEveryone.id,
|
||||
user,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
data: {
|
||||
title: 'Everyone (Update 1)',
|
||||
@@ -365,17 +365,105 @@ describe('Query Presets', () => {
|
||||
|
||||
expect(presetUpdatedByUser1.title).toBe('Everyone (Update 1)')
|
||||
|
||||
const presetUpdatedByUser2 = await payload.update({
|
||||
const presetUpdatedByEditorUser = await payload.update({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
id: presetForEveryone.id,
|
||||
user: user2,
|
||||
user: editorUser,
|
||||
overrideAccess: false,
|
||||
data: {
|
||||
title: 'Everyone (Update 2)',
|
||||
},
|
||||
})
|
||||
|
||||
expect(presetUpdatedByUser2.title).toBe('Everyone (Update 2)')
|
||||
expect(presetUpdatedByEditorUser.title).toBe('Everyone (Update 2)')
|
||||
})
|
||||
|
||||
it('should prevent accidental lockout', async () => {
|
||||
// attempt to create a preset without access to read or update
|
||||
try {
|
||||
const presetWithoutAccess = await payload.create({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
data: {
|
||||
title: 'Prevent Lockout',
|
||||
relatedCollection: 'pages',
|
||||
access: {
|
||||
read: {
|
||||
constraint: 'specificUsers',
|
||||
users: [],
|
||||
},
|
||||
update: {
|
||||
constraint: 'specificUsers',
|
||||
users: [],
|
||||
},
|
||||
delete: {
|
||||
constraint: 'specificUsers',
|
||||
users: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(presetWithoutAccess).toBeFalsy()
|
||||
} catch (error: unknown) {
|
||||
expect((error as Error).message).toBe('Cannot remove yourself from this preset.')
|
||||
}
|
||||
|
||||
const presetWithUser1 = await payload.create({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
data: {
|
||||
title: 'Prevent Lockout',
|
||||
relatedCollection: 'pages',
|
||||
access: {
|
||||
read: {
|
||||
constraint: 'specificUsers',
|
||||
users: [adminUser.id],
|
||||
},
|
||||
update: {
|
||||
constraint: 'specificUsers',
|
||||
users: [adminUser.id],
|
||||
},
|
||||
delete: {
|
||||
constraint: 'specificUsers',
|
||||
users: [adminUser.id],
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// attempt to update the preset to lock the user out of access
|
||||
try {
|
||||
const presetUpdatedByUser1 = await payload.update({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
id: presetWithUser1.id,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
data: {
|
||||
title: 'Prevent Lockout (Updated)',
|
||||
access: {
|
||||
read: {
|
||||
constraint: 'specificUsers',
|
||||
users: [],
|
||||
},
|
||||
update: {
|
||||
constraint: 'specificUsers',
|
||||
users: [],
|
||||
},
|
||||
delete: {
|
||||
constraint: 'specificUsers',
|
||||
users: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(presetUpdatedByUser1).toBeFalsy()
|
||||
} catch (error: unknown) {
|
||||
expect((error as Error).message).toBe('Cannot remove yourself from this preset.')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -383,7 +471,8 @@ describe('Query Presets', () => {
|
||||
it('should respect top-level access control overrides', async () => {
|
||||
const preset = await payload.create({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
user,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
data: {
|
||||
title: 'Top-Level Access Control Override',
|
||||
relatedCollection: 'pages',
|
||||
@@ -404,7 +493,7 @@ describe('Query Presets', () => {
|
||||
const foundPresetWithUser1 = await payload.findByID({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
depth: 0,
|
||||
user,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
id: preset.id,
|
||||
})
|
||||
@@ -412,15 +501,15 @@ describe('Query Presets', () => {
|
||||
expect(foundPresetWithUser1.id).toBe(preset.id)
|
||||
|
||||
try {
|
||||
const foundPresetWithAnonymousUser = await payload.findByID({
|
||||
const foundPresetWithPublicUser = await payload.findByID({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
depth: 0,
|
||||
user: anonymousUser,
|
||||
user: publicUser,
|
||||
overrideAccess: false,
|
||||
id: preset.id,
|
||||
})
|
||||
|
||||
expect(foundPresetWithAnonymousUser).toBeFalsy()
|
||||
expect(foundPresetWithPublicUser).toBeFalsy()
|
||||
} catch (error: unknown) {
|
||||
expect((error as Error).message).toBe('You are not allowed to perform this action.')
|
||||
}
|
||||
@@ -429,7 +518,8 @@ describe('Query Presets', () => {
|
||||
it('should respect access when set to "specificRoles"', async () => {
|
||||
const presetForSpecificRoles = await payload.create({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
user,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
data: {
|
||||
title: 'Specific Roles',
|
||||
where: {
|
||||
@@ -454,7 +544,7 @@ describe('Query Presets', () => {
|
||||
const foundPresetWithUser1 = await payload.findByID({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
depth: 0,
|
||||
user,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
id: presetForSpecificRoles.id,
|
||||
})
|
||||
@@ -462,15 +552,15 @@ describe('Query Presets', () => {
|
||||
expect(foundPresetWithUser1.id).toBe(presetForSpecificRoles.id)
|
||||
|
||||
try {
|
||||
const foundPresetWithUser2 = await payload.findByID({
|
||||
const foundPresetWithEditorUser = await payload.findByID({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
depth: 0,
|
||||
user: user2,
|
||||
user: editorUser,
|
||||
overrideAccess: false,
|
||||
id: presetForSpecificRoles.id,
|
||||
})
|
||||
|
||||
expect(foundPresetWithUser2).toBeFalsy()
|
||||
expect(foundPresetWithEditorUser).toBeFalsy()
|
||||
} catch (error: unknown) {
|
||||
expect((error as Error).message).toBe('Not Found')
|
||||
}
|
||||
@@ -478,7 +568,7 @@ describe('Query Presets', () => {
|
||||
const presetUpdatedByUser1 = await payload.update({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
id: presetForSpecificRoles.id,
|
||||
user,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
data: {
|
||||
title: 'Specific Roles (Updated)',
|
||||
@@ -488,17 +578,17 @@ describe('Query Presets', () => {
|
||||
expect(presetUpdatedByUser1.title).toBe('Specific Roles (Updated)')
|
||||
|
||||
try {
|
||||
const presetUpdatedByUser2 = await payload.update({
|
||||
const presetUpdatedByEditorUser = await payload.update({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
id: presetForSpecificRoles.id,
|
||||
user: user2,
|
||||
user: editorUser,
|
||||
overrideAccess: false,
|
||||
data: {
|
||||
title: 'Specific Roles (Updated)',
|
||||
},
|
||||
})
|
||||
|
||||
expect(presetUpdatedByUser2).toBeFalsy()
|
||||
expect(presetUpdatedByEditorUser).toBeFalsy()
|
||||
} catch (error: unknown) {
|
||||
expect((error as Error).message).toBe('You are not allowed to perform this action.')
|
||||
}
|
||||
@@ -508,7 +598,7 @@ describe('Query Presets', () => {
|
||||
// create a preset with the read constraint set to "noone"
|
||||
const presetForNoone = await payload.create({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
user,
|
||||
user: adminUser,
|
||||
data: {
|
||||
relatedCollection: 'pages',
|
||||
title: 'Noone',
|
||||
@@ -529,7 +619,7 @@ describe('Query Presets', () => {
|
||||
const foundPresetWithUser1 = await payload.findByID({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
depth: 0,
|
||||
user,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
id: presetForNoone.id,
|
||||
})
|
||||
@@ -545,7 +635,8 @@ describe('Query Presets', () => {
|
||||
try {
|
||||
const result = await payload.create({
|
||||
collection: 'payload-query-presets',
|
||||
user,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
data: {
|
||||
title: 'Disabled Query Presets',
|
||||
relatedCollection: 'pages',
|
||||
@@ -563,7 +654,8 @@ describe('Query Presets', () => {
|
||||
it('transforms "where" query objects into the "and" / "or" format', async () => {
|
||||
const result = await payload.create({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
user,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
data: {
|
||||
title: 'Where Object Formatting',
|
||||
where: {
|
||||
|
||||
@@ -68,8 +68,8 @@ export interface Config {
|
||||
blocks: {};
|
||||
collections: {
|
||||
pages: Page;
|
||||
users: User;
|
||||
posts: Post;
|
||||
users: User;
|
||||
'payload-locked-documents': PayloadLockedDocument;
|
||||
'payload-preferences': PayloadPreference;
|
||||
'payload-migrations': PayloadMigration;
|
||||
@@ -78,8 +78,8 @@ export interface Config {
|
||||
collectionsJoins: {};
|
||||
collectionsSelect: {
|
||||
pages: PagesSelect<false> | PagesSelect<true>;
|
||||
users: UsersSelect<false> | UsersSelect<true>;
|
||||
posts: PostsSelect<false> | PostsSelect<true>;
|
||||
users: UsersSelect<false> | UsersSelect<true>;
|
||||
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
|
||||
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
|
||||
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
|
||||
@@ -126,7 +126,16 @@ export interface Page {
|
||||
text?: string | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
_status?: ('draft' | 'published') | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "posts".
|
||||
*/
|
||||
export interface Post {
|
||||
id: string;
|
||||
text?: string | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
@@ -135,7 +144,7 @@ export interface Page {
|
||||
export interface User {
|
||||
id: string;
|
||||
name?: string | null;
|
||||
roles?: ('admin' | 'user' | 'anonymous')[] | null;
|
||||
roles?: ('admin' | 'editor' | 'user')[] | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
email: string;
|
||||
@@ -147,17 +156,6 @@ export interface User {
|
||||
lockUntil?: string | null;
|
||||
password?: string | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "posts".
|
||||
*/
|
||||
export interface Post {
|
||||
id: string;
|
||||
text?: string | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
_status?: ('draft' | 'published') | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-locked-documents".
|
||||
@@ -169,13 +167,13 @@ export interface PayloadLockedDocument {
|
||||
relationTo: 'pages';
|
||||
value: string | Page;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'users';
|
||||
value: string | User;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'posts';
|
||||
value: string | Post;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'users';
|
||||
value: string | User;
|
||||
} | null);
|
||||
globalSlug?: string | null;
|
||||
user: {
|
||||
@@ -231,12 +229,12 @@ export interface PayloadQueryPreset {
|
||||
read?: {
|
||||
constraint?: ('everyone' | 'onlyMe' | 'specificUsers' | 'specificRoles' | 'noone') | null;
|
||||
users?: (string | User)[] | null;
|
||||
roles?: ('admin' | 'user' | 'anonymous')[] | null;
|
||||
roles?: ('admin' | 'editor' | 'user')[] | null;
|
||||
};
|
||||
update?: {
|
||||
constraint?: ('everyone' | 'onlyMe' | 'specificUsers' | 'specificRoles') | null;
|
||||
users?: (string | User)[] | null;
|
||||
roles?: ('admin' | 'user' | 'anonymous')[] | null;
|
||||
roles?: ('admin' | 'editor' | 'user')[] | null;
|
||||
};
|
||||
delete?: {
|
||||
constraint?: ('everyone' | 'onlyMe' | 'specificUsers') | null;
|
||||
@@ -262,6 +260,10 @@ export interface PayloadQueryPreset {
|
||||
| boolean
|
||||
| null;
|
||||
relatedCollection: 'pages' | 'posts';
|
||||
/**
|
||||
* This is a tempoary field used to determine if updating the preset would remove the user's access to it. When `true`, this record will be deleted after running the preset's `validate` function.
|
||||
*/
|
||||
isTemp?: boolean | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
@@ -273,7 +275,15 @@ export interface PagesSelect<T extends boolean = true> {
|
||||
text?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
_status?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "posts_select".
|
||||
*/
|
||||
export interface PostsSelect<T extends boolean = true> {
|
||||
text?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
@@ -292,16 +302,6 @@ export interface UsersSelect<T extends boolean = true> {
|
||||
loginAttempts?: T;
|
||||
lockUntil?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "posts_select".
|
||||
*/
|
||||
export interface PostsSelect<T extends boolean = true> {
|
||||
text?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
_status?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-locked-documents_select".
|
||||
@@ -368,6 +368,7 @@ export interface PayloadQueryPresetsSelect<T extends boolean = true> {
|
||||
where?: T;
|
||||
columns?: T;
|
||||
relatedCollection?: T;
|
||||
isTemp?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
|
||||
@@ -10,11 +10,11 @@ type SeededQueryPreset = {
|
||||
} & Omit<QueryPreset, 'id' | 'relatedCollection'>
|
||||
|
||||
export const seedData: {
|
||||
everyone: SeededQueryPreset
|
||||
onlyMe: SeededQueryPreset
|
||||
specificUsers: (args: { userID: string }) => SeededQueryPreset
|
||||
everyone: () => SeededQueryPreset
|
||||
onlyMe: () => SeededQueryPreset
|
||||
specificUsers: (args: { adminUserID: string }) => SeededQueryPreset
|
||||
} = {
|
||||
onlyMe: {
|
||||
onlyMe: () => ({
|
||||
relatedCollection: pagesSlug,
|
||||
isShared: false,
|
||||
title: 'Only Me',
|
||||
@@ -40,8 +40,8 @@ export const seedData: {
|
||||
equals: 'example page',
|
||||
},
|
||||
},
|
||||
},
|
||||
everyone: {
|
||||
}),
|
||||
everyone: () => ({
|
||||
relatedCollection: pagesSlug,
|
||||
isShared: true,
|
||||
title: 'Everyone',
|
||||
@@ -67,8 +67,8 @@ export const seedData: {
|
||||
equals: 'example page',
|
||||
},
|
||||
},
|
||||
},
|
||||
specificUsers: ({ userID }: { userID: string }) => ({
|
||||
}),
|
||||
specificUsers: ({ adminUserID }: { adminUserID: string }) => ({
|
||||
title: 'Specific Users',
|
||||
isShared: true,
|
||||
where: {
|
||||
@@ -79,15 +79,15 @@ export const seedData: {
|
||||
access: {
|
||||
read: {
|
||||
constraint: 'specificUsers',
|
||||
users: [userID],
|
||||
users: [adminUserID],
|
||||
},
|
||||
update: {
|
||||
constraint: 'specificUsers',
|
||||
users: [userID],
|
||||
users: [adminUserID],
|
||||
},
|
||||
delete: {
|
||||
constraint: 'specificUsers',
|
||||
users: [userID],
|
||||
users: [adminUserID],
|
||||
},
|
||||
},
|
||||
columns: [
|
||||
@@ -101,7 +101,7 @@ export const seedData: {
|
||||
}
|
||||
|
||||
export const seed = async (_payload: Payload) => {
|
||||
const [devUser] = await executePromises(
|
||||
const [adminUser] = await executePromises(
|
||||
[
|
||||
() =>
|
||||
_payload.create({
|
||||
@@ -119,18 +119,18 @@ export const seed = async (_payload: Payload) => {
|
||||
data: {
|
||||
email: regularCredentials.email,
|
||||
password: regularCredentials.password,
|
||||
name: 'User',
|
||||
roles: ['user'],
|
||||
name: 'Editor',
|
||||
roles: ['editor'],
|
||||
},
|
||||
}),
|
||||
() =>
|
||||
_payload.create({
|
||||
collection: usersSlug,
|
||||
data: {
|
||||
email: 'anonymous@email.com',
|
||||
email: 'public@email.com',
|
||||
password: regularCredentials.password,
|
||||
name: 'User',
|
||||
roles: ['anonymous'],
|
||||
name: 'Public User',
|
||||
roles: ['user'],
|
||||
},
|
||||
}),
|
||||
],
|
||||
@@ -149,29 +149,30 @@ export const seed = async (_payload: Payload) => {
|
||||
() =>
|
||||
_payload.create({
|
||||
collection: 'payload-query-presets',
|
||||
user: devUser,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
data: seedData.specificUsers({ userID: devUser?.id || '' }),
|
||||
data: seedData.specificUsers({
|
||||
adminUserID: adminUser?.id || '',
|
||||
}),
|
||||
}),
|
||||
() =>
|
||||
_payload.create({
|
||||
collection: 'payload-query-presets',
|
||||
user: devUser,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
data: seedData.everyone,
|
||||
data: seedData.everyone(),
|
||||
}),
|
||||
() =>
|
||||
_payload.create({
|
||||
collection: 'payload-query-presets',
|
||||
user: devUser,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
data: seedData.onlyMe,
|
||||
data: seedData.onlyMe(),
|
||||
}),
|
||||
() =>
|
||||
_payload.create({
|
||||
collection: 'payload-query-presets',
|
||||
user: devUser,
|
||||
overrideAccess: false,
|
||||
user: adminUser,
|
||||
data: {
|
||||
relatedCollection: 'pages',
|
||||
title: 'Noone',
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@payload-config": ["./test/_community/config.ts"],
|
||||
"@payload-config": ["./test/query-presets/config.ts"],
|
||||
"@payloadcms/admin-bar": ["./packages/admin-bar/src"],
|
||||
"@payloadcms/live-preview": ["./packages/live-preview/src"],
|
||||
"@payloadcms/live-preview-react": ["./packages/live-preview-react/src/index.ts"],
|
||||
|
||||
Reference in New Issue
Block a user