Skip to content

Commit 4e57054

Browse files
authored
fix: ensure scheduled publish restriction (#10317)
1 parent d6d9edc commit 4e57054

File tree

7 files changed

+113
-1
lines changed

7 files changed

+113
-1
lines changed

packages/payload/src/config/sanitize.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ export const sanitizeConfig = async (incomingConfig: Config): Promise<SanitizedC
222222

223223
configWithDefaults.jobs.tasks.push(
224224
getSchedulePublishTask({
225+
adminUserSlug: config.admin.user,
225226
collections: schedulePublishCollections,
226227
globals: schedulePublishGlobals,
227228
}),

packages/payload/src/versions/schedule/job.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1+
import type { User } from '../../auth/types.js'
12
import type { TaskConfig } from '../../queues/config/types/taskTypes.js'
23
import type { SchedulePublishTaskInput } from './types.js'
34

45
type Args = {
6+
adminUserSlug: string
57
collections: string[]
68
globals: string[]
79
}
810

911
export const getSchedulePublishTask = ({
12+
adminUserSlug,
1013
collections,
1114
globals,
1215
}: Args): TaskConfig<{ input: SchedulePublishTaskInput; output: object }> => {
@@ -15,6 +18,20 @@ export const getSchedulePublishTask = ({
1518
handler: async ({ input, req }) => {
1619
const _status = input?.type === 'publish' || !input?.type ? 'published' : 'draft'
1720

21+
const userID = input.user
22+
23+
let user: null | User = null
24+
25+
if (userID) {
26+
user = (await req.payload.findByID({
27+
id: userID,
28+
collection: adminUserSlug,
29+
depth: 0,
30+
})) as User
31+
32+
user.collection = adminUserSlug
33+
}
34+
1835
let publishSpecificLocale: string
1936

2037
if (input?.type === 'publish' && input.locale && req.payload.config.localization) {
@@ -35,7 +52,9 @@ export const getSchedulePublishTask = ({
3552
_status,
3653
},
3754
depth: 0,
55+
overrideAccess: user === null,
3856
publishSpecificLocale,
57+
user,
3958
})
4059
}
4160

@@ -46,7 +65,9 @@ export const getSchedulePublishTask = ({
4665
_status,
4766
},
4867
depth: 0,
68+
overrideAccess: user === null,
4969
publishSpecificLocale,
70+
user,
5071
})
5172
}
5273

@@ -75,6 +96,11 @@ export const getSchedulePublishTask = ({
7596
type: 'select',
7697
options: globals,
7798
},
99+
{
100+
name: 'user',
101+
type: 'relationship',
102+
relationTo: adminUserSlug,
103+
},
78104
],
79105
}
80106
}

packages/payload/src/versions/schedule/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ export type SchedulePublishTaskInput = {
88
global?: GlobalSlug
99
locale?: string
1010
type: string
11+
user?: number | string
1112
}

packages/ui/src/utilities/schedulePublishHandler.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,40 @@ export const schedulePublishHandler = async ({
1111
doc,
1212
global,
1313
locale,
14-
req: { i18n, payload },
14+
req,
1515
}: SchedulePublishHandlerArgs) => {
16+
const { i18n, payload, user } = req
17+
18+
const incomingUserSlug = user?.collection
19+
20+
const adminUserSlug = payload.config.admin.user
21+
22+
if (!incomingUserSlug) {
23+
throw new Error('Unauthorized')
24+
}
25+
26+
const adminAccessFunction = payload.collections[incomingUserSlug].config.access?.admin
27+
28+
// Run the admin access function from the config if it exists
29+
if (adminAccessFunction) {
30+
const canAccessAdmin = await adminAccessFunction({ req })
31+
32+
if (!canAccessAdmin) {
33+
throw new Error('Unauthorized')
34+
}
35+
// Match the user collection to the global admin config
36+
} else if (adminUserSlug !== incomingUserSlug) {
37+
throw new Error('Unauthorized')
38+
}
39+
1640
try {
1741
await payload.jobs.queue({
1842
input: {
1943
type,
2044
doc,
2145
global,
2246
locale,
47+
user: user.id,
2348
},
2449
task: 'schedulePublish',
2550
waitUntil: date,

test/versions/collections/Drafts.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ import { draftCollectionSlug } from '../slugs.js'
55
const DraftPosts: CollectionConfig = {
66
slug: draftCollectionSlug,
77
access: {
8+
update: () => {
9+
return {
10+
restrictedToUpdate: {
11+
not_equals: true,
12+
},
13+
}
14+
},
815
read: ({ req: { user } }) => {
916
if (user) {
1017
return true
@@ -111,6 +118,10 @@ const DraftPosts: CollectionConfig = {
111118
type: 'relationship',
112119
relationTo: draftCollectionSlug,
113120
},
121+
{
122+
name: 'restrictedToUpdate',
123+
type: 'checkbox',
124+
},
114125
],
115126
versions: {
116127
drafts: {

test/versions/int.spec.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1792,6 +1792,51 @@ describe('Versions', () => {
17921792
expect(retrieved._status).toStrictEqual('published')
17931793
})
17941794

1795+
it('should restrict scheduled publish based on user', async () => {
1796+
const draft = await payload.create({
1797+
collection: draftCollectionSlug,
1798+
data: {
1799+
title: 'my doc to publish in the future',
1800+
description: 'hello',
1801+
restrictedToUpdate: true,
1802+
},
1803+
draft: true,
1804+
})
1805+
1806+
expect(draft._status).toStrictEqual('draft')
1807+
1808+
const currentDate = new Date()
1809+
1810+
const user = (
1811+
await payload.find({ collection: 'users', where: { email: { equals: devUser.email } } })
1812+
).docs[0]
1813+
1814+
await payload.jobs.queue({
1815+
task: 'schedulePublish',
1816+
waitUntil: new Date(currentDate.getTime() + 3000),
1817+
input: {
1818+
doc: {
1819+
relationTo: draftCollectionSlug,
1820+
value: draft.id,
1821+
},
1822+
user: user.id,
1823+
},
1824+
})
1825+
1826+
await wait(4000)
1827+
1828+
const res = await payload.jobs.run()
1829+
1830+
expect(res.jobStatus[Object.keys(res.jobStatus)[0]].status).toBe('error-reached-max-retries')
1831+
1832+
const retrieved = await payload.findByID({
1833+
collection: draftCollectionSlug,
1834+
id: draft.id,
1835+
})
1836+
1837+
expect(retrieved._status).toStrictEqual('draft')
1838+
})
1839+
17951840
it('should allow collection scheduled unpublish', async () => {
17961841
const published = await payload.create({
17971842
collection: draftCollectionSlug,

test/versions/payload-types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ export interface DraftPost {
157157
}[]
158158
| null;
159159
relation?: (string | null) | DraftPost;
160+
restrictedToUpdate?: boolean | null;
160161
updatedAt: string;
161162
createdAt: string;
162163
_status?: ('draft' | 'published') | null;
@@ -459,6 +460,7 @@ export interface DraftPostsSelect<T extends boolean = true> {
459460
};
460461
};
461462
relation?: T;
463+
restrictedToUpdate?: T;
462464
updatedAt?: T;
463465
createdAt?: T;
464466
_status?: T;
@@ -723,6 +725,7 @@ export interface TaskSchedulePublish {
723725
value: string | DraftPost;
724726
} | null;
725727
global?: 'draft-global' | null;
728+
user?: (string | null) | User;
726729
};
727730
output?: unknown;
728731
}

0 commit comments

Comments
 (0)