diff --git a/packages/next/src/utilities/auth.ts b/packages/next/src/utilities/auth.ts deleted file mode 100644 index 7a6eb82694..0000000000 --- a/packages/next/src/utilities/auth.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { Payload, PayloadRequest } from 'payload/types' - -import { getAccessResults, getAuthenticatedUser, parseCookies } from 'payload/auth' - -type Args = { - headers: Request['headers'] - payload: Payload -} - -export const auth = async ({ headers, payload }: Args) => { - const cookies = parseCookies(headers) - - const user = await getAuthenticatedUser({ - cookies, - headers, - payload, - }) - - const permissions = await getAccessResults({ - req: { - context: {}, - headers, - i18n: undefined, - payload, - payloadAPI: 'REST', - t: undefined, - user, - } as PayloadRequest, - }) - - return { - cookies, - permissions, - user, - } -} diff --git a/packages/next/src/utilities/createPayloadRequest.ts b/packages/next/src/utilities/createPayloadRequest.ts index 3bebd04e25..1d171d1a57 100644 --- a/packages/next/src/utilities/createPayloadRequest.ts +++ b/packages/next/src/utilities/createPayloadRequest.ts @@ -7,7 +7,7 @@ import type { import { initI18n } from '@payloadcms/translations' import { translations } from '@payloadcms/translations/api' -import { getAuthenticatedUser } from 'payload/auth' +import { executeAuthStrategies } from 'payload/auth' import { parseCookies } from 'payload/auth' import { getDataLoader } from 'payload/utilities' import qs from 'qs' @@ -117,7 +117,7 @@ export const createPayloadRequest = async ({ if (data) req.json = () => Promise.resolve(data) req.payloadDataLoader = getDataLoader(req) - req.user = await getAuthenticatedUser({ + req.user = await executeAuthStrategies({ cookies, headers: req.headers, isGraphQL, diff --git a/packages/next/src/utilities/initPage.ts b/packages/next/src/utilities/initPage.ts index fb2663128e..1704570209 100644 --- a/packages/next/src/utilities/initPage.ts +++ b/packages/next/src/utilities/initPage.ts @@ -12,11 +12,11 @@ import { translations } from '@payloadcms/translations/client' import { findLocaleFromCode } from '@payloadcms/ui/utilities/findLocaleFromCode' import { headers as getHeaders } from 'next/headers.js' import { notFound, redirect } from 'next/navigation.js' +import { parseCookies } from 'payload/auth' import { createLocalReq, isEntityHidden } from 'payload/utilities' import qs from 'qs' import { getPayloadHMR } from '../utilities/getPayloadHMR.js' -import { auth } from './auth.js' import { getRequestLanguage } from './getRequestLanguage.js' type Args = { @@ -35,12 +35,43 @@ export const initPage = async ({ const headers = getHeaders() const localeParam = searchParams?.locale as string const payload = await getPayloadHMR({ config: configPromise }) + const { collections, globals, localization, routes } = payload.config - const { cookies, permissions, user } = await auth({ - headers, - payload, + const queryString = `${qs.stringify(searchParams ?? {}, { addQueryPrefix: true })}` + const defaultLocale = + localization && localization.defaultLocale ? localization.defaultLocale : 'en' + const localeCode = localeParam || defaultLocale + const locale = localization && findLocaleFromCode(localization, localeCode) + const cookies = parseCookies(headers) + const language = getRequestLanguage({ config: payload.config, cookies, headers }) + + const i18n = initI18n({ + config: payload.config.i18n, + context: 'client', + language, + translations, }) + const req = createLocalReq( + { + fallbackLocale: null, + locale: locale.code, + req: { + i18n, + query: qs.parse(queryString, { + depth: 10, + ignoreQueryPrefix: true, + }), + url: `${payload.config.serverURL}${route}${searchParams ? queryString : ''}`, + } as PayloadRequest, + }, + payload, + ) + + const { permissions, user } = await payload.auth({ headers, req }) + + req.user = user + const visibleEntities: VisibleEntities = { collections: payload.config.collections .map(({ slug, admin: { hidden } }) => (!isEntityHidden({ hidden, user }) ? slug : null)) @@ -56,8 +87,6 @@ export const initPage = async ({ const globalSlug = entityType === 'globals' ? entitySlug : undefined const docID = collectionSlug && createOrID !== 'create' ? createOrID : undefined - const { collections, globals, localization, routes } = payload.config - if (redirectUnauthenticatedUser && !user && route !== '/login') { if (searchParams && 'redirect' in searchParams) delete searchParams.redirect @@ -68,38 +97,6 @@ export const initPage = async ({ redirect(`${routes.admin}/login?redirect=${route + stringifiedSearchParams}`) } - const defaultLocale = - localization && localization.defaultLocale ? localization.defaultLocale : 'en' - const localeCode = localeParam || defaultLocale - const locale = localization && findLocaleFromCode(localization, localeCode) - const language = getRequestLanguage({ config: payload.config, cookies, headers }) - - const i18n = initI18n({ - config: payload.config.i18n, - context: 'client', - language, - translations, - }) - - const queryString = `${qs.stringify(searchParams ?? {}, { addQueryPrefix: true })}` - - const req = createLocalReq( - { - fallbackLocale: null, - locale: locale.code, - req: { - i18n, - query: qs.parse(queryString, { - depth: 10, - ignoreQueryPrefix: true, - }), - url: `${payload.config.serverURL}${route}${searchParams ? queryString : ''}`, - } as PayloadRequest, - user, - }, - payload, - ) - let collectionConfig: SanitizedCollectionConfig let globalConfig: SanitizedGlobalConfig diff --git a/packages/payload/src/auth/getAuthenticatedUser.ts b/packages/payload/src/auth/executeAuthStrategies.ts similarity index 89% rename from packages/payload/src/auth/getAuthenticatedUser.ts rename to packages/payload/src/auth/executeAuthStrategies.ts index 99f88e9afe..4d292313fb 100644 --- a/packages/payload/src/auth/getAuthenticatedUser.ts +++ b/packages/payload/src/auth/executeAuthStrategies.ts @@ -1,6 +1,6 @@ import type { AuthStrategyFunctionArgs, User } from './index.js' -export const getAuthenticatedUser = async ( +export const executeAuthStrategies = async ( args: AuthStrategyFunctionArgs, ): Promise => { return args.payload.authStrategies.reduce(async (accumulatorPromise, strategy) => { diff --git a/packages/payload/src/auth/operations/auth.ts b/packages/payload/src/auth/operations/auth.ts new file mode 100644 index 0000000000..1627a01be6 --- /dev/null +++ b/packages/payload/src/auth/operations/auth.ts @@ -0,0 +1,55 @@ +import type { PayloadRequest } from '../../types/index.js' +import type { Permissions, User } from '../types.js' + +import { commitTransaction } from '../../utilities/commitTransaction.js' +import { initTransaction } from '../../utilities/initTransaction.js' +import { killTransaction } from '../../utilities/killTransaction.js' +import { parseCookies } from '../cookies.js' +import { executeAuthStrategies } from '../executeAuthStrategies.js' +import { getAccessResults } from '../getAccessResults.js' + +export type AuthArgs = { + headers: Request['headers'] + req: Omit +} + +export type AuthResult = { + cookies: Map + permissions: Permissions + user: User | null +} + +export const auth = async (args: AuthArgs): Promise => { + const { headers } = args + const req = args.req as PayloadRequest + const { payload } = req + + const cookies = parseCookies(headers) + + try { + const shouldCommit = await initTransaction(req) + + const user = await executeAuthStrategies({ + cookies, + headers, + payload, + }) + + req.user = user + + const permissions = await getAccessResults({ + req, + }) + + if (shouldCommit) await commitTransaction(req) + + return { + cookies, + permissions, + user, + } + } catch (error: unknown) { + await killTransaction(req) + throw error + } +} diff --git a/packages/payload/src/auth/operations/local/auth.ts b/packages/payload/src/auth/operations/local/auth.ts new file mode 100644 index 0000000000..05ddc439f5 --- /dev/null +++ b/packages/payload/src/auth/operations/local/auth.ts @@ -0,0 +1,15 @@ +import type { Payload } from '../../../index.js' +import type { PayloadRequest } from '../../../types/index.js' +import type { AuthArgs, AuthResult } from '../auth.js' + +import { createLocalReq } from '../../../utilities/createLocalReq.js' +import { auth as authOperation } from '../auth.js' + +export const auth = async (payload: Payload, options: AuthArgs): Promise => { + const { headers } = options + + return await authOperation({ + headers, + req: createLocalReq({ req: options.req as PayloadRequest }, payload), + }) +} diff --git a/packages/payload/src/auth/operations/local/index.ts b/packages/payload/src/auth/operations/local/index.ts index 0170fef47d..3268bd641b 100644 --- a/packages/payload/src/auth/operations/local/index.ts +++ b/packages/payload/src/auth/operations/local/index.ts @@ -1,3 +1,4 @@ +import { auth } from './auth.js' import forgotPassword from './forgotPassword.js' import login from './login.js' import resetPassword from './resetPassword.js' @@ -5,6 +6,7 @@ import unlock from './unlock.js' import verifyEmail from './verifyEmail.js' export default { + auth, forgotPassword, login, resetPassword, diff --git a/packages/payload/src/exports/auth.ts b/packages/payload/src/exports/auth.ts index 4f69a27fa5..9838eac56a 100644 --- a/packages/payload/src/exports/auth.ts +++ b/packages/payload/src/exports/auth.ts @@ -1,7 +1,7 @@ export * from '../auth/index.js' export { default as executeAccess } from '../auth/executeAccess.js' +export { executeAuthStrategies } from '../auth/executeAuthStrategies.js' export { getAccessResults } from '../auth/getAccessResults.js' -export { getAuthenticatedUser } from '../auth/getAuthenticatedUser.js' export type { AuthStrategyFunction, diff --git a/packages/payload/src/index.ts b/packages/payload/src/index.ts index 062c9a3330..ddc62d82cb 100644 --- a/packages/payload/src/index.ts +++ b/packages/payload/src/index.ts @@ -5,6 +5,7 @@ import type pino from 'pino' import crypto from 'crypto' +import type { AuthArgs } from './auth/operations/auth.js' import type { Result as ForgotPasswordResult } from './auth/operations/forgotPassword.js' import type { Options as ForgotPasswordOptions } from './auth/operations/local/forgotPassword.js' import type { Options as LoginOptions } from './auth/operations/local/login.js' @@ -59,6 +60,17 @@ import { serverInit as serverInitTelemetry } from './utilities/telemetry/events/ * @description Payload */ export class BasePayload { + /** + * @description Authorization and Authentication using headers and cookies to run auth user strategies + * @returns cookies: Map + * @returns permissions: Permissions + * @returns user: User + */ + auth = async (options: AuthArgs) => { + const { auth } = localOperations.auth + return auth(this, options) + } + authStrategies: AuthStrategy[] collections: { diff --git a/test/versions/collections/Versions.ts b/test/versions/collections/Versions.ts index 7869ea7a29..369824ab42 100644 --- a/test/versions/collections/Versions.ts +++ b/test/versions/collections/Versions.ts @@ -4,15 +4,6 @@ import { versionCollectionSlug } from '../slugs.js' const VersionPosts: CollectionConfig = { slug: versionCollectionSlug, - admin: { - useAsTitle: 'title', - defaultColumns: ['title', 'description', 'createdAt'], - preview: () => 'https://payloadcms.com', - }, - versions: { - drafts: false, - maxPerDoc: 2, - }, access: { read: ({ req: { user } }) => { if (user) { @@ -36,22 +27,31 @@ const VersionPosts: CollectionConfig = { }, readVersions: ({ req: { user } }) => Boolean(user), }, + admin: { + defaultColumns: ['title', 'description', 'createdAt'], + preview: () => 'https://payloadcms.com', + useAsTitle: 'title', + }, fields: [ { name: 'title', - label: 'Title', type: 'text', + label: 'Title', + localized: true, required: true, unique: true, - localized: true, }, { name: 'description', - label: 'Description', type: 'textarea', + label: 'Description', required: true, }, ], + versions: { + drafts: false, + maxPerDoc: 2, + }, } export default VersionPosts