Files
payload/packages/next/src/utilities/initPage/index.ts
Paul 01ccbd48b0 feat!: custom views are now public by default and fixed some issues with notFound page (#8820)
This PR aims to fix a few issues with the notFound page and custom views
so it matches v2 behaviour:
- Non authorised users should always be redirected to the login page
regardless if not found or valid URL
- Previously notFound would render for non users too potentially
exposing valid but protected routes and creating a confusing workflow as
the UI was being rendered as well
- Custom views are now public by default
- in our `admin` test suite, the `/admin/public-custom-view` is
accessible to non users but
`/admin/public-custom-view/protected-nested-view` is not unless the
checkbox is true in the Settings global, there's e2e coverage for this
- Fixes https://github.com/payloadcms/payload/issues/8716
2024-10-30 11:29:29 -06:00

173 lines
4.6 KiB
TypeScript

import type { InitPageResult, Locale, PayloadRequest, VisibleEntities } from 'payload'
import { findLocaleFromCode } from '@payloadcms/ui/shared'
import { headers as getHeaders } from 'next/headers.js'
import { notFound } from 'next/navigation.js'
import { createLocalReq, isEntityHidden, parseCookies } from 'payload'
import * as qs from 'qs-esm'
import type { Args } from './types.js'
import { getPayloadHMR } from '../getPayloadHMR.js'
import { initReq } from '../initReq.js'
import { getRouteInfo } from './handleAdminPage.js'
import { handleAuthRedirect } from './handleAuthRedirect.js'
import { isCustomAdminView } from './isCustomAdminView.js'
import { isPublicAdminRoute } from './shared.js'
export const initPage = async ({
config: configPromise,
importMap,
route,
searchParams,
}: Args): Promise<InitPageResult> => {
const headers = await getHeaders()
const payload = await getPayloadHMR({ config: configPromise, importMap })
const queryString = `${qs.stringify(searchParams ?? {}, { addQueryPrefix: true })}`
const {
collections,
globals,
localization,
routes: { admin: adminRoute },
} = payload.config
const cookies = parseCookies(headers)
const { i18n, permissions, user } = await initReq(payload.config)
// Ideally, we should not need to recreate the req, because
// we can get it from the above initReq.
// We just need to -overwrite- the url and query of the req
// we get above. Clone the req? We'll look into that eventually.
const req = await createLocalReq(
{
fallbackLocale: null,
req: {
headers,
host: headers.get('host'),
i18n,
query: qs.parse(queryString, {
depth: 10,
ignoreQueryPrefix: true,
}),
url: `${payload.config.serverURL}${route}${searchParams ? queryString : ''}`,
} as PayloadRequest,
},
payload,
)
const languageOptions = Object.entries(payload.config.i18n.supportedLanguages || {}).reduce(
(acc, [language, languageConfig]) => {
if (Object.keys(payload.config.i18n.supportedLanguages).includes(language)) {
acc.push({
label: languageConfig.translations.general.thisLanguage,
value: language,
})
}
return acc
},
[],
)
req.user = user
const localeParam = searchParams?.locale as string
let locale: Locale
if (localization) {
const defaultLocaleCode = localization.defaultLocale ? localization.defaultLocale : 'en'
let localeCode: string = localeParam
if (!localeCode) {
try {
localeCode = await payload
.find({
collection: 'payload-preferences',
depth: 0,
limit: 1,
user,
where: {
and: [
{
'user.relationTo': {
equals: payload.config.admin.user,
},
},
{
'user.value': {
equals: user.id,
},
},
{
key: {
equals: 'locale',
},
},
],
},
})
?.then((res) => res.docs?.[0]?.value as string)
} catch (error) {} // eslint-disable-line no-empty
}
locale = findLocaleFromCode(localization, localeCode)
if (!locale) {
locale = findLocaleFromCode(localization, defaultLocaleCode)
}
req.locale = locale.code
}
const visibleEntities: VisibleEntities = {
collections: collections
.map(({ slug, admin: { hidden } }) => (!isEntityHidden({ hidden, user }) ? slug : null))
.filter(Boolean),
globals: globals
.map(({ slug, admin: { hidden } }) => (!isEntityHidden({ hidden, user }) ? slug : null))
.filter(Boolean),
}
let redirectTo = null
if (
!permissions.canAccessAdmin &&
!isPublicAdminRoute({ adminRoute, config: payload.config, route }) &&
!isCustomAdminView({ adminRoute, config: payload.config, route })
) {
redirectTo = handleAuthRedirect({
config: payload.config,
route,
searchParams,
user,
})
}
const { collectionConfig, collectionSlug, docID, globalConfig, globalSlug } = getRouteInfo({
adminRoute,
config: payload.config,
defaultIDType: payload.db.defaultIDType,
route,
})
if ((collectionSlug && !collectionConfig) || (globalSlug && !globalConfig)) {
return notFound()
}
return {
collectionConfig,
cookies,
docID,
globalConfig,
languageOptions,
locale,
permissions,
redirectTo,
req,
translations: i18n.translations,
visibleEntities,
}
}