Files
payloadcms/test/auth/config.ts
Jacob Fletcher 8113d3bdef fix(next): exclude permissions from page response when unauthenticated (#13796)
Similar spirit as #13714.

Permissions are embedded into the page response, exposing some field
names to unauthenticated users.

For example, when setting `read: () => false` on a field, that field's
name is now included in the response due to its presence in the
permissions object.

We now search the HTML source directly in the test, similar to "view
source" in the browser, which will be much effective at preventing
regression going forward.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211347942663256
2025-09-12 20:57:03 +00:00

315 lines
7.7 KiB
TypeScript

import { fileURLToPath } from 'node:url'
import path from 'path'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
import { devUser } from '../credentials.js'
import { seed } from './seed.js'
import {
apiKeysSlug,
namedSaveToJWTValue,
partialDisableLocalStrategiesSlug,
publicUsersSlug,
saveToJWTKey,
slug,
} from './shared.js'
export default buildConfigWithDefaults({
admin: {
autoLogin: {
email: devUser.email,
password: devUser.password,
prefillOnly: true,
},
components: {
beforeDashboard: ['./BeforeDashboard.js#BeforeDashboard'],
beforeLogin: ['./BeforeLogin.js#BeforeLogin'],
},
importMap: {
baseDir: path.resolve(dirname),
},
user: 'users',
},
collections: [
{
slug,
admin: {
useAsTitle: 'custom',
},
auth: {
cookies: {
domain: undefined,
sameSite: 'Lax',
secure: false,
},
depth: 0,
lockTime: 600 * 1000, // lock time in ms
maxLoginAttempts: 2,
tokenExpiration: 7200, // 2 hours
useAPIKey: true,
verify: false,
forgotPassword: {
expiration: 300000, // 5 minutes
},
},
fields: [
{
name: 'adminOnlyField',
type: 'text',
access: {
read: ({ req: { user } }) => {
return user?.roles?.includes('admin')
},
},
},
{
name: 'roles',
type: 'select',
defaultValue: ['user'],
hasMany: true,
label: 'Role',
options: ['admin', 'editor', 'moderator', 'user', 'viewer'],
required: true,
saveToJWT: true,
},
{
name: 'namedSaveToJWT',
type: 'text',
defaultValue: namedSaveToJWTValue,
label: 'Named Save To JWT',
saveToJWT: saveToJWTKey,
},
{
name: 'richText',
type: 'richText',
},
{
name: 'group',
type: 'group',
fields: [
{
name: 'liftedSaveToJWT',
type: 'text',
defaultValue: 'lifted from group',
label: 'Lifted Save To JWT',
saveToJWT: 'x-lifted-from-group',
},
],
},
{
name: 'groupSaveToJWT',
type: 'group',
fields: [
{
name: 'saveToJWTString',
type: 'text',
defaultValue: 'nested property',
label: 'Save To JWT String',
saveToJWT: 'x-test',
},
{
name: 'saveToJWTFalse',
type: 'text',
defaultValue: 'nested property',
label: 'Save To JWT False',
saveToJWT: false,
},
],
label: 'Group Save To JWT',
saveToJWT: 'x-group',
},
{
type: 'tabs',
tabs: [
{
name: 'saveToJWTTab',
fields: [
{
name: 'test',
type: 'text',
defaultValue: 'yes',
saveToJWT: 'x-field',
},
],
label: 'Save To JWT Tab',
saveToJWT: true,
},
{
name: 'tabSaveToJWTString',
fields: [
{
name: 'includedByDefault',
type: 'text',
defaultValue: 'yes',
},
],
label: 'Tab Save To JWT String',
saveToJWT: 'tab-test',
},
{
fields: [
{
name: 'tabLiftedSaveToJWT',
type: 'text',
defaultValue: 'lifted from unnamed tab',
label: 'Tab Lifted Save To JWT',
saveToJWT: true,
},
{
name: 'unnamedTabSaveToJWTString',
type: 'text',
defaultValue: 'text',
label: 'Unnamed Tab Save To JWT String',
saveToJWT: 'x-tab-field',
},
{
name: 'unnamedTabSaveToJWTFalse',
type: 'text',
defaultValue: 'false',
label: 'Unnamed Tab Save To JWT False',
saveToJWT: false,
},
],
label: 'No Name',
},
],
},
{
name: 'custom',
type: 'text',
label: 'Custom',
},
{
name: 'authDebug',
type: 'ui',
admin: {
components: {
Field: '/AuthDebug.js#AuthDebug',
},
},
label: 'Auth Debug',
},
{
// This is a uniquely identifiable field that we use to ensure it doesn't appear in the page source when unauthenticated
// E.g. if the user is authenticated, it will appear in the both the client config
name: 'shouldNotShowInClientConfigUnlessAuthenticated',
type: 'text',
access: {
// Setting this forces the field to show up in the permissions object
read: () => true,
},
},
],
},
{
slug: partialDisableLocalStrategiesSlug,
auth: {
disableLocalStrategy: {
// optionalPassword: true,
enableFields: true,
},
},
fields: [
// with `enableFields: true`, the following DB columns will be created:
// email
// reset_password_token
// reset_password_expiration
// salt
// hash
// login_attempts
// lock_until
],
},
{
slug: 'disable-local-strategy-password',
auth: { disableLocalStrategy: true },
fields: [
{
name: 'password',
type: 'text',
required: true,
},
],
},
{
slug: apiKeysSlug,
access: {
read: ({ req: { user } }) => {
if (!user) {
return false
}
if (user?.collection === apiKeysSlug) {
return {
id: {
equals: user.id,
},
}
}
return true
},
},
auth: {
disableLocalStrategy: true,
useAPIKey: true,
},
fields: [],
labels: {
plural: 'API Keys',
singular: 'API Key',
},
},
{
slug: publicUsersSlug,
auth: {
verify: true,
},
fields: [],
},
{
slug: 'relationsCollection',
fields: [
{
name: 'rel',
type: 'relationship',
relationTo: 'users',
},
{
name: 'text',
type: 'text',
},
],
},
{
slug: 'api-keys-with-field-read-access',
auth: {
disableLocalStrategy: true,
useAPIKey: true,
},
fields: [
{
name: 'enableAPIKey',
type: 'checkbox',
access: {
read: () => false,
},
},
{
name: 'apiKey',
type: 'text',
access: {
read: () => false,
},
},
],
labels: {
plural: 'API Keys With Field Read Access',
singular: 'API Key With Field Read Access',
},
},
],
onInit: seed,
typescript: {
outputFile: path.resolve(dirname, 'payload-types.ts'),
},
})