fix: fully sanitize unauthenticated client config (#13785)

Follow-up to #13714.

Fully sanitizes the unauthenticated client config to exclude much of the
users collection, including fields, etc. These are not required of the
login flow and are now completely omitted along with other unnecessary
properties.

This is closely aligned with the goals of the original PR, and as an
added bonus, makes the config _even smaller_ than it already was for
unauthenticated users.

Needs #13790.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211332845301588
This commit is contained in:
Jacob Fletcher
2025-09-12 14:52:50 -04:00
committed by GitHub
parent b62a30a8dc
commit e2632c86d0
10 changed files with 108 additions and 7 deletions

View File

@@ -0,0 +1,16 @@
'use client'
import { useConfig } from '@payloadcms/ui'
export const BeforeDashboard = () => {
const { config } = useConfig()
return (
<p
id="authenticated-client-config"
style={{ opacity: 0, pointerEvents: 'none', position: 'absolute' }}
>
{JSON.stringify(config, null, 2)}
</p>
)
}

16
test/auth/BeforeLogin.tsx Normal file
View File

@@ -0,0 +1,16 @@
'use client'
import { useConfig } from '@payloadcms/ui'
export const BeforeLogin = () => {
const { config } = useConfig()
return (
<p
id="unauthenticated-client-config"
style={{ opacity: 0, pointerEvents: 'none', position: 'absolute' }}
>
{JSON.stringify(config, null, 2)}
</p>
)
}

View File

@@ -21,6 +21,10 @@ export default buildConfigWithDefaults({
password: devUser.password,
prefillOnly: true,
},
components: {
beforeDashboard: ['./BeforeDashboard.js#BeforeDashboard'],
beforeLogin: ['./BeforeLogin.js#BeforeLogin'],
},
importMap: {
baseDir: path.resolve(dirname),
},
@@ -185,6 +189,10 @@ export default buildConfigWithDefaults({
},
label: 'Auth Debug',
},
{
name: 'shouldNotShowInClientConfigUnlessAuthenticated',
type: 'text',
},
],
},
{

View File

@@ -182,6 +182,32 @@ describe('Auth', () => {
await saveDocAndAssert(page, '#action-save')
})
test('should protect the client config behind authentication', async () => {
await logout(page, serverURL)
// This element is absolutely positioned and `opacity: 0`
await expect(page.locator('#unauthenticated-client-config')).toBeAttached()
// Search for our uniquely identifiable field name
await expect(
page.locator('#unauthenticated-client-config', {
hasText: 'shouldNotShowInClientConfigUnlessAuthenticated',
}),
).toHaveCount(0)
await login({ page, serverURL })
await page.goto(serverURL + '/admin')
await expect(page.locator('#authenticated-client-config')).toBeAttached()
await expect(
page.locator('#authenticated-client-config', {
hasText: 'shouldNotShowInClientConfigUnlessAuthenticated',
}),
).toHaveCount(1)
})
test('should allow change password', async () => {
await page.goto(url.account)
const emailBeforeSave = await page.locator('#field-email').inputValue()

View File

@@ -348,6 +348,7 @@ export interface ApiKey {
*/
export interface PublicUser {
id: string;
shouldNotShowInClientConfigUnlessAuthenticated?: string | null;
updatedAt: string;
createdAt: string;
email: string;
@@ -611,6 +612,7 @@ export interface ApiKeysSelect<T extends boolean = true> {
* via the `definition` "public-users_select".
*/
export interface PublicUsersSelect<T extends boolean = true> {
shouldNotShowInClientConfigUnlessAuthenticated?: T;
updatedAt?: T;
createdAt?: T;
email?: T;

2
test/next-env.d.ts vendored
View File

@@ -2,4 +2,4 @@
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.