From 4e57054bb7d419973a2f6d3f47474652b62d8d81 Mon Sep 17 00:00:00 2001 From: Sasha <64744993+r1tsuu@users.noreply.github.com> Date: Fri, 3 Jan 2025 15:26:21 +0200 Subject: [PATCH] fix: ensure scheduled publish restriction (#10317) --- packages/payload/src/config/sanitize.ts | 1 + packages/payload/src/versions/schedule/job.ts | 26 +++++++++++ .../payload/src/versions/schedule/types.ts | 1 + .../src/utilities/schedulePublishHandler.ts | 27 ++++++++++- test/versions/collections/Drafts.ts | 11 +++++ test/versions/int.spec.ts | 45 +++++++++++++++++++ test/versions/payload-types.ts | 3 ++ 7 files changed, 113 insertions(+), 1 deletion(-) diff --git a/packages/payload/src/config/sanitize.ts b/packages/payload/src/config/sanitize.ts index 26dac0daa..305c1ec6b 100644 --- a/packages/payload/src/config/sanitize.ts +++ b/packages/payload/src/config/sanitize.ts @@ -222,6 +222,7 @@ export const sanitizeConfig = async (incomingConfig: Config): Promise => { @@ -15,6 +18,20 @@ export const getSchedulePublishTask = ({ handler: async ({ input, req }) => { const _status = input?.type === 'publish' || !input?.type ? 'published' : 'draft' + const userID = input.user + + let user: null | User = null + + if (userID) { + user = (await req.payload.findByID({ + id: userID, + collection: adminUserSlug, + depth: 0, + })) as User + + user.collection = adminUserSlug + } + let publishSpecificLocale: string if (input?.type === 'publish' && input.locale && req.payload.config.localization) { @@ -35,7 +52,9 @@ export const getSchedulePublishTask = ({ _status, }, depth: 0, + overrideAccess: user === null, publishSpecificLocale, + user, }) } @@ -46,7 +65,9 @@ export const getSchedulePublishTask = ({ _status, }, depth: 0, + overrideAccess: user === null, publishSpecificLocale, + user, }) } @@ -75,6 +96,11 @@ export const getSchedulePublishTask = ({ type: 'select', options: globals, }, + { + name: 'user', + type: 'relationship', + relationTo: adminUserSlug, + }, ], } } diff --git a/packages/payload/src/versions/schedule/types.ts b/packages/payload/src/versions/schedule/types.ts index b57f4433f..3e16c10d9 100644 --- a/packages/payload/src/versions/schedule/types.ts +++ b/packages/payload/src/versions/schedule/types.ts @@ -8,4 +8,5 @@ export type SchedulePublishTaskInput = { global?: GlobalSlug locale?: string type: string + user?: number | string } diff --git a/packages/ui/src/utilities/schedulePublishHandler.ts b/packages/ui/src/utilities/schedulePublishHandler.ts index de2bf1134..3dc0fc772 100644 --- a/packages/ui/src/utilities/schedulePublishHandler.ts +++ b/packages/ui/src/utilities/schedulePublishHandler.ts @@ -11,8 +11,32 @@ export const schedulePublishHandler = async ({ doc, global, locale, - req: { i18n, payload }, + req, }: SchedulePublishHandlerArgs) => { + const { i18n, payload, user } = req + + const incomingUserSlug = user?.collection + + const adminUserSlug = payload.config.admin.user + + if (!incomingUserSlug) { + throw new Error('Unauthorized') + } + + const adminAccessFunction = payload.collections[incomingUserSlug].config.access?.admin + + // Run the admin access function from the config if it exists + if (adminAccessFunction) { + const canAccessAdmin = await adminAccessFunction({ req }) + + if (!canAccessAdmin) { + throw new Error('Unauthorized') + } + // Match the user collection to the global admin config + } else if (adminUserSlug !== incomingUserSlug) { + throw new Error('Unauthorized') + } + try { await payload.jobs.queue({ input: { @@ -20,6 +44,7 @@ export const schedulePublishHandler = async ({ doc, global, locale, + user: user.id, }, task: 'schedulePublish', waitUntil: date, diff --git a/test/versions/collections/Drafts.ts b/test/versions/collections/Drafts.ts index 4927e80c3..d1655d083 100644 --- a/test/versions/collections/Drafts.ts +++ b/test/versions/collections/Drafts.ts @@ -5,6 +5,13 @@ import { draftCollectionSlug } from '../slugs.js' const DraftPosts: CollectionConfig = { slug: draftCollectionSlug, access: { + update: () => { + return { + restrictedToUpdate: { + not_equals: true, + }, + } + }, read: ({ req: { user } }) => { if (user) { return true @@ -111,6 +118,10 @@ const DraftPosts: CollectionConfig = { type: 'relationship', relationTo: draftCollectionSlug, }, + { + name: 'restrictedToUpdate', + type: 'checkbox', + }, ], versions: { drafts: { diff --git a/test/versions/int.spec.ts b/test/versions/int.spec.ts index ab10e4108..2464ab892 100644 --- a/test/versions/int.spec.ts +++ b/test/versions/int.spec.ts @@ -1792,6 +1792,51 @@ describe('Versions', () => { expect(retrieved._status).toStrictEqual('published') }) + it('should restrict scheduled publish based on user', async () => { + const draft = await payload.create({ + collection: draftCollectionSlug, + data: { + title: 'my doc to publish in the future', + description: 'hello', + restrictedToUpdate: true, + }, + draft: true, + }) + + expect(draft._status).toStrictEqual('draft') + + const currentDate = new Date() + + const user = ( + await payload.find({ collection: 'users', where: { email: { equals: devUser.email } } }) + ).docs[0] + + await payload.jobs.queue({ + task: 'schedulePublish', + waitUntil: new Date(currentDate.getTime() + 3000), + input: { + doc: { + relationTo: draftCollectionSlug, + value: draft.id, + }, + user: user.id, + }, + }) + + await wait(4000) + + const res = await payload.jobs.run() + + expect(res.jobStatus[Object.keys(res.jobStatus)[0]].status).toBe('error-reached-max-retries') + + const retrieved = await payload.findByID({ + collection: draftCollectionSlug, + id: draft.id, + }) + + expect(retrieved._status).toStrictEqual('draft') + }) + it('should allow collection scheduled unpublish', async () => { const published = await payload.create({ collection: draftCollectionSlug, diff --git a/test/versions/payload-types.ts b/test/versions/payload-types.ts index a30f7d0ec..9ac130116 100644 --- a/test/versions/payload-types.ts +++ b/test/versions/payload-types.ts @@ -157,6 +157,7 @@ export interface DraftPost { }[] | null; relation?: (string | null) | DraftPost; + restrictedToUpdate?: boolean | null; updatedAt: string; createdAt: string; _status?: ('draft' | 'published') | null; @@ -459,6 +460,7 @@ export interface DraftPostsSelect { }; }; relation?: T; + restrictedToUpdate?: T; updatedAt?: T; createdAt?: T; _status?: T; @@ -723,6 +725,7 @@ export interface TaskSchedulePublish { value: string | DraftPost; } | null; global?: 'draft-global' | null; + user?: (string | null) | User; }; output?: unknown; }