Skip to content

Commit b766ae6

Browse files
authored
fix: globals with versions return _status field when access denied (#14406)
### What? Fixes an issue where globals with drafts/versions enabled would return the `_status` field with its default value (`'draft'`) when access control denied read access, instead of returning an empty object. ### Why? When a global has: 1. Versions/drafts enabled (which adds the `_status` field with `defaultValue: 'draft'`) 2. Read access control with a where clause (e.g., `return {_status: {equals: 'published'}}`) And a user without permissions requests the global (e.g., an unpublished draft exists), the API was returning `{"_status": "draft"}` instead of an empty object `{}`. This exposes the document's status even when access is denied. The issue occurred because when the DB query returned `null` (filtered by access control), the `findOne` operation would set `doc = {}`, and then the `afterRead` hook would populate default values including `_status: 'draft'`. ### How? - Modified `afterRead/promise.ts` to skip setting default values for globals when: - The document has no `id` (indicating it doesn't exist or was filtered) - AND access control is active (`!overrideAccess`) Fixes #14096
1 parent 8e5e23a commit b766ae6

File tree

4 files changed

+43
-6
lines changed

4 files changed

+43
-6
lines changed

packages/payload/src/fields/hooks/afterRead/promise.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -373,12 +373,19 @@ export const promise = async ({
373373
}
374374

375375
// Set defaultValue on the field for globals being returned without being first created
376-
// or collection documents created prior to having a default
376+
// or collection documents created prior to having a default.
377+
378+
// Skip setting defaults when: global has no ID (never created or filtered by access)
379+
// AND access control is active (not overriding). This prevents default values like
380+
// `_status: 'draft'` from appearing when access control filters out the document.
381+
const shouldSkipDefaults = global && !doc.id && !overrideAccess
382+
377383
if (
378384
!removedFieldValue &&
379385
allowDefaultValue &&
380386
typeof siblingDoc[field.name!] === 'undefined' &&
381-
typeof field.defaultValue !== 'undefined'
387+
typeof field.defaultValue !== 'undefined' &&
388+
!shouldSkipDefaults
382389
) {
383390
siblingDoc[field.name!] = await getDefaultValue({
384391
defaultValue: field.defaultValue,

test/versions/globals/DraftWithMax.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { draftWithMaxGlobalSlug } from '../slugs.js'
44

55
const DraftWithMaxGlobal: GlobalConfig = {
66
slug: draftWithMaxGlobalSlug,
7-
label: 'Draft Global',
7+
label: 'Draft with Max Global',
88
admin: {
99
components: {
1010
views: {

test/versions/int.spec.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2554,6 +2554,36 @@ describe('Versions', () => {
25542554
expect(retrieved.title).toStrictEqual('i will be a draft')
25552555
})
25562556

2557+
it('should not return _status field when access control denies read', async () => {
2558+
// Create a draft global
2559+
const draft = await payload.updateGlobal({
2560+
slug: draftGlobalSlug,
2561+
data: {
2562+
_status: 'draft',
2563+
title: 'draft only',
2564+
},
2565+
draft: true,
2566+
})
2567+
2568+
expect(draft._status).toStrictEqual('draft')
2569+
2570+
// Create a request without a user (simulating unauthenticated request)
2571+
// Access control on draftGlobalSlug requires published status when no user
2572+
const req = await createLocalReq({}, payload)
2573+
req.user = null
2574+
2575+
const result = await payload.findGlobal({
2576+
slug: draftGlobalSlug,
2577+
overrideAccess: false,
2578+
req,
2579+
})
2580+
2581+
// Should return empty object, not {_status: 'draft'}
2582+
// The _status field should not be populated with its default value
2583+
expect(Object.keys(result)).toHaveLength(0)
2584+
expect(result._status).toBeUndefined()
2585+
})
2586+
25572587
describe('server functions', () => {
25582588
let draftDoc
25592589
let event

test/versions/payload-types.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ export interface AutosavePost {
209209
root: {
210210
type: string;
211211
children: {
212-
type: string;
212+
type: any;
213213
version: number;
214214
[k: string]: unknown;
215215
}[];
@@ -514,7 +514,7 @@ export interface Diff {
514514
root: {
515515
type: string;
516516
children: {
517-
type: string;
517+
type: any;
518518
version: number;
519519
[k: string]: unknown;
520520
}[];
@@ -529,7 +529,7 @@ export interface Diff {
529529
root: {
530530
type: string;
531531
children: {
532-
type: string;
532+
type: any;
533533
version: number;
534534
[k: string]: unknown;
535535
}[];

0 commit comments

Comments
 (0)