chore: consolidate canAccessAdmin logic (#13849)
Consolidates the logic for admin access control for server functions, etc. behind a standard `canAccessAdmin` function.
This commit is contained in:
@@ -3,7 +3,7 @@ import type { DocumentPreferences, VisibleEntities } from 'payload'
|
||||
|
||||
import { getClientConfig } from '@payloadcms/ui/utilities/getClientConfig'
|
||||
import { headers as getHeaders } from 'next/headers.js'
|
||||
import { getAccessResults, isEntityHidden, parseCookies } from 'payload'
|
||||
import { canAccessAdmin, getAccessResults, isEntityHidden, parseCookies } from 'payload'
|
||||
|
||||
import { renderDocument } from './index.js'
|
||||
|
||||
@@ -35,38 +35,7 @@ export const renderDocumentHandler: RenderDocumentServerFunction = async (args)
|
||||
|
||||
const cookies = parseCookies(headers)
|
||||
|
||||
const incomingUserSlug = user?.collection
|
||||
|
||||
const adminUserSlug = config.admin.user
|
||||
|
||||
// If we have a user slug, test it against the functions
|
||||
if (incomingUserSlug) {
|
||||
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')
|
||||
}
|
||||
} else {
|
||||
const hasUsers = await payload.find({
|
||||
collection: adminUserSlug,
|
||||
depth: 0,
|
||||
limit: 1,
|
||||
pagination: false,
|
||||
})
|
||||
|
||||
// If there are users, we should not allow access because of /create-first-user
|
||||
if (hasUsers.docs.length) {
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
}
|
||||
await canAccessAdmin({ req })
|
||||
|
||||
const clientConfig = getClientConfig({
|
||||
config,
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { CollectionPreferences, ListQuery, ServerFunction, VisibleEntities
|
||||
|
||||
import { getClientConfig } from '@payloadcms/ui/utilities/getClientConfig'
|
||||
import { headers as getHeaders } from 'next/headers.js'
|
||||
import { getAccessResults, isEntityHidden, parseCookies } from 'payload'
|
||||
import { canAccessAdmin, getAccessResults, isEntityHidden, parseCookies } from 'payload'
|
||||
|
||||
import { renderListView } from './index.js'
|
||||
|
||||
@@ -53,38 +53,7 @@ export const renderListHandler: ServerFunction<
|
||||
|
||||
const cookies = parseCookies(headers)
|
||||
|
||||
const incomingUserSlug = user?.collection
|
||||
|
||||
const adminUserSlug = config.admin.user
|
||||
|
||||
// If we have a user slug, test it against the functions
|
||||
if (incomingUserSlug) {
|
||||
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')
|
||||
}
|
||||
} else {
|
||||
const hasUsers = await payload.find({
|
||||
collection: adminUserSlug,
|
||||
depth: 0,
|
||||
limit: 1,
|
||||
pagination: false,
|
||||
})
|
||||
|
||||
// If there are users, we should not allow access because of /create-first-user
|
||||
if (hasUsers.docs.length) {
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
}
|
||||
await canAccessAdmin({ req })
|
||||
|
||||
const clientConfig = getClientConfig({
|
||||
config,
|
||||
|
||||
@@ -1658,6 +1658,7 @@ export { _internal_safeFetchGlobal } from './uploads/safeFetch.js'
|
||||
export type * from './uploads/types.js'
|
||||
export { addDataAndFileToRequest } from './utilities/addDataAndFileToRequest.js'
|
||||
export { addLocalesToRequestFromData, sanitizeLocales } from './utilities/addLocalesToRequest.js'
|
||||
export { canAccessAdmin } from './utilities/canAccessAdmin.js'
|
||||
export { commitTransaction } from './utilities/commitTransaction.js'
|
||||
export {
|
||||
configToJSONSchema,
|
||||
|
||||
41
packages/payload/src/utilities/canAccessAdmin.ts
Normal file
41
packages/payload/src/utilities/canAccessAdmin.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import type { PayloadRequest } from '../types/index.js'
|
||||
|
||||
/**
|
||||
* Protects admin-only routes, server functions, etc.
|
||||
* The requesting user must either:
|
||||
* a. pass the `access.admin` function on the `users` collection, if defined
|
||||
* b. match the `config.admin.user` property on the Payload config
|
||||
* c. if no user is present, and there are no users in the system, allow access (for first user creation)
|
||||
* @throws {Error} Throws an `Unauthorized` error if access is denied that can be explicitly caught
|
||||
*/
|
||||
export const canAccessAdmin = async ({ req }: { req: PayloadRequest }) => {
|
||||
const incomingUserSlug = req.user?.collection
|
||||
const adminUserSlug = req.payload.config.admin.user
|
||||
|
||||
if (incomingUserSlug) {
|
||||
const adminAccessFn = req.payload.collections[incomingUserSlug]?.config.access?.admin
|
||||
|
||||
if (adminAccessFn) {
|
||||
const canAccess = await adminAccessFn({ req })
|
||||
|
||||
if (!canAccess) {
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
// Match the user collection to the global admin config
|
||||
} else if (adminUserSlug !== incomingUserSlug) {
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
} else {
|
||||
const hasUsers = await req.payload.find({
|
||||
collection: adminUserSlug,
|
||||
depth: 0,
|
||||
limit: 1,
|
||||
pagination: false,
|
||||
})
|
||||
|
||||
// If there are users, we should not allow access because of `/create-first-user`
|
||||
if (hasUsers.docs.length) {
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import type {
|
||||
ServerFunction,
|
||||
} from 'payload'
|
||||
|
||||
import { formatErrors } from 'payload'
|
||||
import { canAccessAdmin, formatErrors } from 'payload'
|
||||
import { getSelectMode, reduceFieldsToValues } from 'payload/shared'
|
||||
|
||||
import { fieldSchemasToFormState } from '../forms/fieldSchemasToFormState/index.js'
|
||||
@@ -49,40 +49,10 @@ export const buildFormStateHandler: ServerFunction<
|
||||
> = async (args) => {
|
||||
const { req } = args
|
||||
|
||||
const incomingUserSlug = req.user?.collection
|
||||
const adminUserSlug = req.payload.config.admin.user
|
||||
|
||||
try {
|
||||
// If we have a user slug, test it against the functions
|
||||
if (incomingUserSlug) {
|
||||
const adminAccessFunction = req.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')
|
||||
}
|
||||
} else {
|
||||
const hasUsers = await req.payload.find({
|
||||
collection: adminUserSlug,
|
||||
depth: 0,
|
||||
limit: 1,
|
||||
pagination: false,
|
||||
})
|
||||
|
||||
// If there are users, we should not allow access because of /create-first-user
|
||||
if (hasUsers.docs.length) {
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
}
|
||||
|
||||
await canAccessAdmin({ req })
|
||||
const res = await buildFormState(args)
|
||||
|
||||
return res
|
||||
} catch (err) {
|
||||
req.payload.logger.error({ err, msg: `There was an error building form state` })
|
||||
|
||||
@@ -11,7 +11,7 @@ import type {
|
||||
Where,
|
||||
} from 'payload'
|
||||
|
||||
import { APIError, formatErrors } from 'payload'
|
||||
import { APIError, canAccessAdmin, formatErrors } from 'payload'
|
||||
import { isNumber } from 'payload/shared'
|
||||
|
||||
import { getClientConfig } from './getClientConfig.js'
|
||||
@@ -91,39 +91,7 @@ const buildTableState = async (
|
||||
tableAppearance,
|
||||
} = args
|
||||
|
||||
const incomingUserSlug = user?.collection
|
||||
|
||||
const adminUserSlug = config.admin.user
|
||||
|
||||
// If we have a user slug, test it against the functions
|
||||
if (incomingUserSlug) {
|
||||
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')
|
||||
}
|
||||
} else {
|
||||
const hasUsers = await payload.find({
|
||||
collection: adminUserSlug,
|
||||
depth: 0,
|
||||
limit: 1,
|
||||
pagination: false,
|
||||
})
|
||||
|
||||
// If there are users, we should not allow access because of /create-first-user
|
||||
if (hasUsers.docs.length) {
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
}
|
||||
await canAccessAdmin({ req })
|
||||
|
||||
const clientConfig = getClientConfig({
|
||||
config,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import ObjectIdImport from 'bson-objectid'
|
||||
import {
|
||||
canAccessAdmin,
|
||||
type CollectionSlug,
|
||||
type Data,
|
||||
type Field,
|
||||
@@ -245,26 +246,7 @@ export const copyDataFromLocale = async (args: CopyDataFromLocaleArgs) => {
|
||||
toLocale,
|
||||
} = args
|
||||
|
||||
const incomingUserSlug = user?.collection
|
||||
|
||||
const adminUserSlug = payload.config.admin.user
|
||||
|
||||
// If we have a user slug, test it against the functions
|
||||
if (incomingUserSlug) {
|
||||
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: args.req })
|
||||
|
||||
if (!canAccessAdmin) {
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
// Match the user collection to the global admin config
|
||||
} else if (adminUserSlug !== incomingUserSlug) {
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
}
|
||||
await canAccessAdmin({ req })
|
||||
|
||||
const [fromLocaleData, toLocaleData] = await Promise.allSettled([
|
||||
globalSlug
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { PayloadRequest, SchedulePublishTaskInput } from 'payload'
|
||||
import { canAccessAdmin, type PayloadRequest, type SchedulePublishTaskInput } from 'payload'
|
||||
|
||||
export type SchedulePublishHandlerArgs = {
|
||||
date?: Date
|
||||
@@ -22,27 +22,7 @@ export const schedulePublishHandler = async ({
|
||||
}: 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')
|
||||
}
|
||||
await canAccessAdmin({ req })
|
||||
|
||||
try {
|
||||
if (deleteID) {
|
||||
|
||||
Reference in New Issue
Block a user