From 6b349378e082d174bfa0e1c804e1f3a85e9e8885 Mon Sep 17 00:00:00 2001 From: Jessica Chowdhury <67977755+JessChowdhury@users.noreply.github.com> Date: Mon, 14 Apr 2025 09:47:08 +0100 Subject: [PATCH] feat: adds and exports reusable auth server functions (#11900) ### What Adds exportable server functions for `login`, `logout` and `refresh` that are fully typed and ready to use. ### Why Creating server functions for these auth operations require the developer to manually set and handle the cookies / auth JWT. This can be a complex and involved process - instead we want to provide an option that will handle the cookies internally and simplify the process for the user. ### How Three re-usable functions can be exported from `@payload/next/server-functions`: - login - logout - refresh Examples of how to use these functions will be added to the docs shortly, along with more in-depth info on server functions. --- docs/authentication/operations.mdx | 29 ++- docs/local-api/server-functions.mdx | 163 +++++++++++- packages/next/package.json | 5 + packages/next/src/auth/login.ts | 87 +++++++ packages/next/src/auth/logout.ts | 29 +++ packages/next/src/auth/refresh.ts | 42 +++ packages/next/src/exports/auth.ts | 3 + .../src/utilities/getExistingAuthToken.ts | 10 + .../src/utilities/setPayloadAuthCookie.ts | 42 +++ test/server-functions/components/login.tsx | 36 +++ .../components/loginFunction.tsx | 23 ++ test/server-functions/components/logout.tsx | 8 + .../components/logoutFunction.tsx | 14 + test/server-functions/components/refresh.tsx | 7 + .../components/refreshFunction.tsx | 16 ++ test/server-functions/config.ts | 34 +++ test/server-functions/e2e.spec.ts | 97 +++++++ test/server-functions/eslint.config.js | 19 ++ test/server-functions/payload-types.ts | 242 ++++++++++++++++++ test/server-functions/tsconfig.eslint.json | 13 + test/server-functions/tsconfig.json | 8 + 21 files changed, 922 insertions(+), 5 deletions(-) create mode 100644 packages/next/src/auth/login.ts create mode 100644 packages/next/src/auth/logout.ts create mode 100644 packages/next/src/auth/refresh.ts create mode 100644 packages/next/src/exports/auth.ts create mode 100644 packages/next/src/utilities/getExistingAuthToken.ts create mode 100644 packages/next/src/utilities/setPayloadAuthCookie.ts create mode 100644 test/server-functions/components/login.tsx create mode 100644 test/server-functions/components/loginFunction.tsx create mode 100644 test/server-functions/components/logout.tsx create mode 100644 test/server-functions/components/logoutFunction.tsx create mode 100644 test/server-functions/components/refresh.tsx create mode 100644 test/server-functions/components/refreshFunction.tsx create mode 100644 test/server-functions/config.ts create mode 100644 test/server-functions/e2e.spec.ts create mode 100644 test/server-functions/eslint.config.js create mode 100644 test/server-functions/payload-types.ts create mode 100644 test/server-functions/tsconfig.eslint.json create mode 100644 test/server-functions/tsconfig.json diff --git a/docs/authentication/operations.mdx b/docs/authentication/operations.mdx index 026b4221dc..88c81baaf9 100644 --- a/docs/authentication/operations.mdx +++ b/docs/authentication/operations.mdx @@ -158,7 +158,7 @@ mutation { ```ts const result = await payload.login({ - collection: '[collection-slug]', + collection: 'collection-slug', data: { email: 'dev@payloadcms.com', password: 'get-out', @@ -166,6 +166,13 @@ const result = await payload.login({ }) ``` + + **Server Functions:** Payload offers a ready-to-use `login` server function + that utilizes the Local API. For integration details and examples, check out + the [Server Function + docs](../local-api/server-functions#reusable-payload-server-functions). + + ## Logout As Payload sets HTTP-only cookies, logging out cannot be done by just removing a cookie in JavaScript, as HTTP-only cookies are inaccessible by JS within the browser. So, Payload exposes a `logout` operation to delete the token in a safe way. @@ -189,6 +196,13 @@ mutation { } ``` + + **Server Functions:** Payload provides a ready-to-use `logout` server function + that manages the user's cookie for a seamless logout. For integration details + and examples, check out the [Server Function + docs](../local-api/server-functions#reusable-payload-server-functions). + + ## Refresh Allows for "refreshing" JWTs. If your user has a token that is about to expire, but the user is still active and using the app, you might want to use the `refresh` operation to receive a new token by executing this operation via the authenticated user. @@ -240,6 +254,13 @@ mutation { } ``` + + **Server Functions:** Payload exports a ready-to-use `refresh` server function + that automatically renews the user's token and updates the associated cookie. + For integration details and examples, check out the [Server Function + docs](../local-api/server-functions#reusable-payload-server-functions). + + ## Verify by Email If your collection supports email verification, the Verify operation will be exposed which accepts a verification token and sets the user's `_verified` property to `true`, thereby allowing the user to authenticate with the Payload API. @@ -270,7 +291,7 @@ mutation { ```ts const result = await payload.verifyEmail({ - collection: '[collection-slug]', + collection: 'collection-slug', token: 'TOKEN_HERE', }) ``` @@ -308,7 +329,7 @@ mutation { ```ts const result = await payload.unlock({ - collection: '[collection-slug]', + collection: 'collection-slug', }) ``` @@ -349,7 +370,7 @@ mutation { ```ts const token = await payload.forgotPassword({ - collection: '[collection-slug]', + collection: 'collection-slug', data: { email: 'dev@payloadcms.com', }, diff --git a/docs/local-api/server-functions.mdx b/docs/local-api/server-functions.mdx index dcd6708c77..b8e4950df4 100644 --- a/docs/local-api/server-functions.mdx +++ b/docs/local-api/server-functions.mdx @@ -310,7 +310,168 @@ export const PostForm: React.FC = () => { ## Reusable Payload Server Functions -Coming soon… +Managing authentication with the Local API can be tricky as you have to handle cookies and tokens yourself, and there aren't built-in logout or refresh functions since these only modify cookies. To make this easier, we provide `login`, `logout`, and `refresh` as ready-to-use server functions. They take care of the underlying complexity so you don't have to. + +### `login` + +Logs in a user by verifying credentials and setting the authentication cookie. This function allows login via username or email, depending on the collection auth configuration. + +#### Importing the `login` function + +```ts +import { login } from '@payloadcms/next/auth' +``` + +The login function needs your Payload config, which cannot be imported in a client component. To work around this, create a simple server function like the one below, and call it from your client. + +```ts +'use server' + +import { login } from '@payloadcms/next/auth' +import config from '@payload-config' + +export async function loginAction({ + email, + password, +}: { + email: string + password: string +}) { + try { + const result = await login({ + collection: 'users', + config, + email, + password, + }) + return result + } catch (error) { + throw new Error( + `Login failed: ${error instanceof Error ? error.message : 'Unknown error'}`, + ) + } +} +``` + +#### Login from the React Client Component + +```tsx +'use client' +import { useState } from 'react' +import { loginAction } from '../loginAction' + +export default function LoginForm() { + const [email, setEmail] = useState('') + const [password, setPassword] = useState('') + + return ( +
loginAction({ email, password })}> + + ) => + setEmail(e.target.value) + } + type="email" + value={email} + /> + + ) => + setPassword(e.target.value) + } + type="password" + value={password} + /> + +
+ ) +} +``` + +### `logout` + +Logs out the current user by clearing the authentication cookie. + +#### Importing the `logout` function + +```ts +import { logout } from '@payloadcms/next/auth' +``` + +Similar to the login function, you now need to pass your Payload config to this function and this cannot be done in a client component. Use a helper server function as shown below. + +```ts +'use server' + +import { logout } from '@payloadcms/next/auth' +import config from '@payload-config' + +export async function logoutAction() { + try { + return await logout({ config }) + } catch (error) { + throw new Error( + `Logout failed: ${error instanceof Error ? error.message : 'Unknown error'}`, + ) + } +} +``` + +#### Logout from the React Client Component + +```tsx +'use client' +import { logoutAction } from '../logoutAction' + +export default function LogoutButton() { + return +} +``` + +### `refresh` + +Refreshes the authentication token for the logged-in user. + +#### Importing the `refresh` function + +```ts +import { refresh } from '@payloadcms/next/auth' +``` + +As with login and logout, you need to pass your Payload config to this function. Create a helper server function like the one below. Passing the config directly to the client is not possible and will throw errors. + +```ts +'use server' + +import { refresh } from '@payloadcms/next/auth' +import config from '@payload-config' + +export async function refreshAction() { + try { + return await refresh({ + collection: 'users', // update this to your collection slug + config, + }) + } catch (error) { + throw new Error( + `Refresh failed: ${error instanceof Error ? error.message : 'Unknown error'}`, + ) + } +} +``` + +#### Using Refresh from the React Client Component + +```tsx +'use client' +import { refreshAction } from '../actions/refreshAction' + +export default function RefreshTokenButton() { + return +} +``` ## Error Handling in Server Functions diff --git a/packages/next/package.json b/packages/next/package.json index 73d085cef5..b93b208a9a 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -37,6 +37,11 @@ "types": "./src/exports/routes.ts", "default": "./src/exports/routes.ts" }, + "./auth": { + "import": "./src/exports/auth.ts", + "types": "./src/exports/auth.ts", + "default": "./src/exports/auth.ts" + }, "./templates": { "import": "./src/exports/templates.ts", "types": "./src/exports/templates.ts", diff --git a/packages/next/src/auth/login.ts b/packages/next/src/auth/login.ts new file mode 100644 index 0000000000..c85cd31dd4 --- /dev/null +++ b/packages/next/src/auth/login.ts @@ -0,0 +1,87 @@ +'use server' + +import type { CollectionSlug } from 'payload' + +import { cookies as getCookies } from 'next/headers.js' +import { generatePayloadCookie, getPayload } from 'payload' + +import { setPayloadAuthCookie } from '../utilities/setPayloadAuthCookie.js' + +type LoginWithEmail = { + collection: CollectionSlug + config: any + email: string + password: string + username?: never +} + +type LoginWithUsername = { + collection: CollectionSlug + config: any + email?: never + password: string + username: string +} +type LoginArgs = LoginWithEmail | LoginWithUsername + +export async function login({ collection, config, email, password, username }: LoginArgs): Promise<{ + token?: string + user: any +}> { + const payload = await getPayload({ config }) + + const authConfig = payload.collections[collection]?.config.auth + if (!authConfig) { + throw new Error(`No auth config found for collection: ${collection}`) + } + + const loginWithUsername = authConfig?.loginWithUsername ?? false + + if (loginWithUsername) { + if (loginWithUsername.allowEmailLogin) { + if (!email && !username) { + throw new Error('Email or username is required.') + } + } else { + if (!username) { + throw new Error('Username is required.') + } + } + } else { + if (!email) { + throw new Error('Email is required.') + } + } + + let loginData + + if (loginWithUsername) { + loginData = username ? { password, username } : { email, password } + } else { + loginData = { email, password } + } + + try { + const result = await payload.login({ + collection, + data: loginData, + }) + + if (result.token) { + await setPayloadAuthCookie({ + authConfig, + cookiePrefix: payload.config.cookiePrefix, + token: result.token, + }) + } + + if ('removeTokenFromResponses' in config && config.removeTokenFromResponses) { + delete result.token + } + + return result + } catch (e) { + console.error('Login error:', e) + throw new Error(`${e}`) + } +} diff --git a/packages/next/src/auth/logout.ts b/packages/next/src/auth/logout.ts new file mode 100644 index 0000000000..fc220fb88e --- /dev/null +++ b/packages/next/src/auth/logout.ts @@ -0,0 +1,29 @@ +'use server' + +import { cookies as getCookies, headers as nextHeaders } from 'next/headers.js' +import { getPayload } from 'payload' + +import { getExistingAuthToken } from '../utilities/getExistingAuthToken.js' + +export async function logout({ config }: { config: any }) { + try { + const payload = await getPayload({ config }) + const headers = await nextHeaders() + const result = await payload.auth({ headers }) + + if (!result.user) { + return { message: 'User already logged out', success: true } + } + + const existingCookie = await getExistingAuthToken(payload.config.cookiePrefix) + + if (existingCookie) { + const cookies = await getCookies() + cookies.delete(existingCookie.name) + return { message: 'User logged out successfully', success: true } + } + } catch (e) { + console.error('Logout error:', e) + throw new Error(`${e}`) + } +} diff --git a/packages/next/src/auth/refresh.ts b/packages/next/src/auth/refresh.ts new file mode 100644 index 0000000000..920a4c558d --- /dev/null +++ b/packages/next/src/auth/refresh.ts @@ -0,0 +1,42 @@ +'use server' + +import type { CollectionSlug } from 'payload' + +import { headers as nextHeaders } from 'next/headers.js' +import { getPayload } from 'payload' + +import { getExistingAuthToken } from '../utilities/getExistingAuthToken.js' +import { setPayloadAuthCookie } from '../utilities/setPayloadAuthCookie.js' + +export async function refresh({ collection, config }: { collection: CollectionSlug; config: any }) { + try { + const payload = await getPayload({ config }) + const authConfig = payload.collections[collection]?.config.auth + + if (!authConfig) { + throw new Error(`No auth config found for collection: ${collection}`) + } + + const { user } = await payload.auth({ headers: await nextHeaders() }) + if (!user) { + throw new Error('User not authenticated') + } + + const existingCookie = await getExistingAuthToken(payload.config.cookiePrefix) + + if (!existingCookie) { + return { message: 'No valid token found', success: false } + } + + await setPayloadAuthCookie({ + authConfig, + cookiePrefix: payload.config.cookiePrefix, + token: existingCookie.value, + }) + + return { message: 'Token refreshed successfully', success: true } + } catch (e) { + console.error('Refresh error:', e) + throw new Error(`${e}`) + } +} diff --git a/packages/next/src/exports/auth.ts b/packages/next/src/exports/auth.ts new file mode 100644 index 0000000000..6f6d9f8b1d --- /dev/null +++ b/packages/next/src/exports/auth.ts @@ -0,0 +1,3 @@ +export { login } from '../auth/login.js' +export { logout } from '../auth/logout.js' +export { refresh } from '../auth/refresh.js' diff --git a/packages/next/src/utilities/getExistingAuthToken.ts b/packages/next/src/utilities/getExistingAuthToken.ts new file mode 100644 index 0000000000..a88db01bf0 --- /dev/null +++ b/packages/next/src/utilities/getExistingAuthToken.ts @@ -0,0 +1,10 @@ +import { cookies as getCookies } from 'next/headers.js' + +type Cookie = { + name: string + value: string +} +export async function getExistingAuthToken(cookiePrefix: string): Promise { + const cookies = await getCookies() + return cookies.getAll().find((cookie) => cookie.name.startsWith(cookiePrefix)) +} diff --git a/packages/next/src/utilities/setPayloadAuthCookie.ts b/packages/next/src/utilities/setPayloadAuthCookie.ts new file mode 100644 index 0000000000..4a3a9e0621 --- /dev/null +++ b/packages/next/src/utilities/setPayloadAuthCookie.ts @@ -0,0 +1,42 @@ +import type { Auth } from 'payload' + +import { cookies as getCookies } from 'next/headers.js' +import { generatePayloadCookie } from 'payload' + +type SetPayloadAuthCookieArgs = { + authConfig: Auth + cookiePrefix: string + token: string +} + +export async function setPayloadAuthCookie({ + authConfig, + cookiePrefix, + token, +}: SetPayloadAuthCookieArgs): Promise { + const cookies = await getCookies() + + const cookieExpiration = authConfig.tokenExpiration + ? new Date(Date.now() + authConfig.tokenExpiration) + : undefined + + const payloadCookie = generatePayloadCookie({ + collectionAuthConfig: authConfig, + cookiePrefix, + expires: cookieExpiration, + returnCookieAsObject: true, + token, + }) + + if (payloadCookie.value) { + cookies.set(payloadCookie.name, payloadCookie.value, { + domain: authConfig.cookies.domain, + expires: payloadCookie.expires ? new Date(payloadCookie.expires) : undefined, + httpOnly: true, + sameSite: (typeof authConfig.cookies.sameSite === 'string' + ? authConfig.cookies.sameSite.toLowerCase() + : 'lax') as 'lax' | 'none' | 'strict', + secure: authConfig.cookies.secure || false, + }) + } +} diff --git a/test/server-functions/components/login.tsx b/test/server-functions/components/login.tsx new file mode 100644 index 0000000000..bb2663d2e2 --- /dev/null +++ b/test/server-functions/components/login.tsx @@ -0,0 +1,36 @@ +'use client' + +import { type ChangeEvent, type FormEvent, useState } from 'react' + +import { loginFunction } from './loginFunction.js' + +const LoginForm = () => { + const [email, setEmail] = useState('') + const [password, setPassword] = useState('') + + return ( +
loginFunction({ email, password })}> + + ) => setEmail(e.target.value)} + placeholder="Email" + required + type="email" + value={email} + /> + + ) => setPassword(e.target.value)} + placeholder="Password" + required + type="password" + value={password} + /> + +
+ ) +} + +export default LoginForm diff --git a/test/server-functions/components/loginFunction.tsx b/test/server-functions/components/loginFunction.tsx new file mode 100644 index 0000000000..6c439c1c9e --- /dev/null +++ b/test/server-functions/components/loginFunction.tsx @@ -0,0 +1,23 @@ +'use server' +import { login } from '@payloadcms/next/auth' + +import config from '../config.js' + +type LoginArgs = { + email: string + password: string +} + +export async function loginFunction({ email, password }: LoginArgs) { + try { + const result = await login({ + collection: 'users', + config, + email, + password, + }) + return result + } catch (error) { + throw new Error(`Login failed: ${error instanceof Error ? error.message : 'Unknown error'}`) + } +} diff --git a/test/server-functions/components/logout.tsx b/test/server-functions/components/logout.tsx new file mode 100644 index 0000000000..9af29bf0f6 --- /dev/null +++ b/test/server-functions/components/logout.tsx @@ -0,0 +1,8 @@ +'use client' + +import { logoutFunction } from './logoutFunction.js' + +const LogoutButton = () => { + return +} +export default LogoutButton diff --git a/test/server-functions/components/logoutFunction.tsx b/test/server-functions/components/logoutFunction.tsx new file mode 100644 index 0000000000..85e8ae3c96 --- /dev/null +++ b/test/server-functions/components/logoutFunction.tsx @@ -0,0 +1,14 @@ +'use server' +import { logout } from '@payloadcms/next/auth' + +import config from '../config.js' + +export async function logoutFunction() { + try { + return await logout({ + config, + }) + } catch (error) { + throw new Error(`Logout failed: ${error instanceof Error ? error.message : 'Unknown error'}`) + } +} diff --git a/test/server-functions/components/refresh.tsx b/test/server-functions/components/refresh.tsx new file mode 100644 index 0000000000..08286b08cb --- /dev/null +++ b/test/server-functions/components/refresh.tsx @@ -0,0 +1,7 @@ +'use client' +import { refreshFunction } from './refreshFunction.js' + +const RefreshToken = () => { + return +} +export default RefreshToken diff --git a/test/server-functions/components/refreshFunction.tsx b/test/server-functions/components/refreshFunction.tsx new file mode 100644 index 0000000000..ed830bc5c1 --- /dev/null +++ b/test/server-functions/components/refreshFunction.tsx @@ -0,0 +1,16 @@ +'use server' + +import { refresh } from '@payloadcms/next/auth' + +import config from '../config.js' + +export async function refreshFunction() { + try { + return await refresh({ + collection: 'users', // update this to your collection slug + config, + }) + } catch (error) { + throw new Error(`Refresh failed: ${error instanceof Error ? error.message : 'Unknown error'}`) + } +} diff --git a/test/server-functions/config.ts b/test/server-functions/config.ts new file mode 100644 index 0000000000..f5e5c1c0d3 --- /dev/null +++ b/test/server-functions/config.ts @@ -0,0 +1,34 @@ +import { fileURLToPath } from 'node:url' +import path from 'path' + +import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js' +import { devUser } from '../credentials.js' + +const filename = fileURLToPath(import.meta.url) +const dirname = path.dirname(filename) + +export default buildConfigWithDefaults({ + collections: [], + admin: { + autoLogin: false, + components: { + beforeLogin: ['/components/login.js'], + header: ['/components/refresh.js', '/components/logout.js'], + }, + importMap: { + baseDir: path.resolve(dirname), + }, + }, + onInit: async (payload) => { + await payload.create({ + collection: 'users', + data: { + email: devUser.email, + password: devUser.password, + }, + }) + }, + typescript: { + outputFile: path.resolve(dirname, 'payload-types.ts'), + }, +}) diff --git a/test/server-functions/e2e.spec.ts b/test/server-functions/e2e.spec.ts new file mode 100644 index 0000000000..1c05657c94 --- /dev/null +++ b/test/server-functions/e2e.spec.ts @@ -0,0 +1,97 @@ +import type { Page } from '@playwright/test' + +import { expect, test } from '@playwright/test' +import { devUser } from 'credentials.js' +import path from 'path' +import { fileURLToPath } from 'url' + +import type { PayloadTestSDK } from '../helpers/sdk/index.js' +import type { Config } from './payload-types.js' + +import { ensureCompilationIsDone, initPageConsoleErrorCatch } from '../helpers.js' +import { AdminUrlUtil } from '../helpers/adminUrlUtil.js' +import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js' +import { TEST_TIMEOUT_LONG } from '../playwright.config.js' + +const filename = fileURLToPath(import.meta.url) +const dirname = path.dirname(filename) + +let payload: PayloadTestSDK + +const { beforeAll, describe } = test + +describe('Server Functions', () => { + let page: Page + let url: AdminUrlUtil + let serverURL: string + + beforeAll(async ({ browser }, testInfo) => { + testInfo.setTimeout(TEST_TIMEOUT_LONG) + ;({ payload, serverURL } = await initPayloadE2ENoConfig({ dirname })) + url = new AdminUrlUtil(serverURL, 'users') + + const context = await browser.newContext() + page = await context.newPage() + initPageConsoleErrorCatch(page) + + await ensureCompilationIsDone({ + page, + serverURL, + noAutoLogin: true, + }) + }) + + describe('Auth functions', () => { + test('should log user in from login server function', async () => { + await page.goto(`${serverURL}/admin`) + + // Expect email and password fields to be visible + await expect(page.locator('#email')).toBeVisible() + await expect(page.locator('#password')).toBeVisible() + + await page.fill('#email', devUser.email) + await page.fill('#password', devUser.password) + + const loginButton = page.locator('text=Custom Login') + await expect(loginButton).toBeVisible() + await loginButton.click() + await page.waitForTimeout(1000) + + await page.reload() + await page.goto(`${serverURL}/admin/account`) + await expect(page.locator('h1[title="dev@payloadcms.com"]')).toBeVisible() + }) + + test('should refresh user from refresh server function', async () => { + await page.goto(`${serverURL}/admin`) + + const initialCookie = await page.context().cookies() + const payloadToken = initialCookie.find((cookie) => cookie.name === 'payload-token') + expect(payloadToken).toBeDefined() + const initialExpiry = payloadToken?.expires + + const refreshButton = page.locator('text=Custom Refresh') + await expect(refreshButton).toBeVisible() + await refreshButton.click() + await page.waitForTimeout(1000) + + const updatedCookie = await page.context().cookies() + const updatedPayloadToken = updatedCookie.find((cookie) => cookie.name === 'payload-token') + expect(updatedPayloadToken).toBeDefined() + expect(updatedPayloadToken?.expires).not.toBe(initialExpiry) + }) + + test('should log user out from logout server function', async () => { + await page.goto(`${serverURL}/admin`) + const logoutButton = page.locator('text=Custom Logout') + await expect(logoutButton).toBeVisible() + await logoutButton.click() + await page.waitForTimeout(1000) + + await page.reload() + await page.goto(`${serverURL}/admin`) + await expect(page.locator('#email')).toBeVisible() + await expect(page.locator('#password')).toBeVisible() + }) + }) +}) diff --git a/test/server-functions/eslint.config.js b/test/server-functions/eslint.config.js new file mode 100644 index 0000000000..f295df083f --- /dev/null +++ b/test/server-functions/eslint.config.js @@ -0,0 +1,19 @@ +import { rootParserOptions } from '../../eslint.config.js' +import { testEslintConfig } from '../eslint.config.js' + +/** @typedef {import('eslint').Linter.Config} Config */ + +/** @type {Config[]} */ +export const index = [ + ...testEslintConfig, + { + languageOptions: { + parserOptions: { + ...rootParserOptions, + tsconfigRootDir: import.meta.dirname, + }, + }, + }, +] + +export default index diff --git a/test/server-functions/payload-types.ts b/test/server-functions/payload-types.ts new file mode 100644 index 0000000000..60c5bb7ab4 --- /dev/null +++ b/test/server-functions/payload-types.ts @@ -0,0 +1,242 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * This file was automatically generated by Payload. + * DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config, + * and re-run `payload generate:types` to regenerate this file. + */ + +/** + * Supported timezones in IANA format. + * + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "supportedTimezones". + */ +export type SupportedTimezones = + | 'Pacific/Midway' + | 'Pacific/Niue' + | 'Pacific/Honolulu' + | 'Pacific/Rarotonga' + | 'America/Anchorage' + | 'Pacific/Gambier' + | 'America/Los_Angeles' + | 'America/Tijuana' + | 'America/Denver' + | 'America/Phoenix' + | 'America/Chicago' + | 'America/Guatemala' + | 'America/New_York' + | 'America/Bogota' + | 'America/Caracas' + | 'America/Santiago' + | 'America/Buenos_Aires' + | 'America/Sao_Paulo' + | 'Atlantic/South_Georgia' + | 'Atlantic/Azores' + | 'Atlantic/Cape_Verde' + | 'Europe/London' + | 'Europe/Berlin' + | 'Africa/Lagos' + | 'Europe/Athens' + | 'Africa/Cairo' + | 'Europe/Moscow' + | 'Asia/Riyadh' + | 'Asia/Dubai' + | 'Asia/Baku' + | 'Asia/Karachi' + | 'Asia/Tashkent' + | 'Asia/Calcutta' + | 'Asia/Dhaka' + | 'Asia/Almaty' + | 'Asia/Jakarta' + | 'Asia/Bangkok' + | 'Asia/Shanghai' + | 'Asia/Singapore' + | 'Asia/Tokyo' + | 'Asia/Seoul' + | 'Australia/Brisbane' + | 'Australia/Sydney' + | 'Pacific/Guam' + | 'Pacific/Noumea' + | 'Pacific/Auckland' + | 'Pacific/Fiji'; + +export interface Config { + auth: { + users: UserAuthOperations; + }; + blocks: {}; + collections: { + users: User; + 'payload-locked-documents': PayloadLockedDocument; + 'payload-preferences': PayloadPreference; + 'payload-migrations': PayloadMigration; + }; + collectionsJoins: {}; + collectionsSelect: { + users: UsersSelect | UsersSelect; + 'payload-locked-documents': PayloadLockedDocumentsSelect | PayloadLockedDocumentsSelect; + 'payload-preferences': PayloadPreferencesSelect | PayloadPreferencesSelect; + 'payload-migrations': PayloadMigrationsSelect | PayloadMigrationsSelect; + }; + db: { + defaultIDType: string; + }; + globals: {}; + globalsSelect: {}; + locale: null; + user: User & { + collection: 'users'; + }; + jobs: { + tasks: unknown; + workflows: unknown; + }; +} +export interface UserAuthOperations { + forgotPassword: { + email: string; + password: string; + }; + login: { + email: string; + password: string; + }; + registerFirstUser: { + email: string; + password: string; + }; + unlock: { + email: string; + password: string; + }; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "users". + */ +export interface User { + id: string; + updatedAt: string; + createdAt: string; + email: string; + resetPasswordToken?: string | null; + resetPasswordExpiration?: string | null; + salt?: string | null; + hash?: string | null; + loginAttempts?: number | null; + lockUntil?: string | null; + password?: string | null; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "payload-locked-documents". + */ +export interface PayloadLockedDocument { + id: string; + document?: { + relationTo: 'users'; + value: string | User; + } | null; + globalSlug?: string | null; + user: { + relationTo: 'users'; + value: string | User; + }; + updatedAt: string; + createdAt: string; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "payload-preferences". + */ +export interface PayloadPreference { + id: string; + user: { + relationTo: 'users'; + value: string | User; + }; + key?: string | null; + value?: + | { + [k: string]: unknown; + } + | unknown[] + | string + | number + | boolean + | null; + updatedAt: string; + createdAt: string; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "payload-migrations". + */ +export interface PayloadMigration { + id: string; + name?: string | null; + batch?: number | null; + updatedAt: string; + createdAt: string; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "users_select". + */ +export interface UsersSelect { + updatedAt?: T; + createdAt?: T; + email?: T; + resetPasswordToken?: T; + resetPasswordExpiration?: T; + salt?: T; + hash?: T; + loginAttempts?: T; + lockUntil?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "payload-locked-documents_select". + */ +export interface PayloadLockedDocumentsSelect { + document?: T; + globalSlug?: T; + user?: T; + updatedAt?: T; + createdAt?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "payload-preferences_select". + */ +export interface PayloadPreferencesSelect { + user?: T; + key?: T; + value?: T; + updatedAt?: T; + createdAt?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "payload-migrations_select". + */ +export interface PayloadMigrationsSelect { + name?: T; + batch?: T; + updatedAt?: T; + createdAt?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "auth". + */ +export interface Auth { + [k: string]: unknown; +} + + +declare module 'payload' { + // @ts-ignore + export interface GeneratedTypes extends Config {} +} \ No newline at end of file diff --git a/test/server-functions/tsconfig.eslint.json b/test/server-functions/tsconfig.eslint.json new file mode 100644 index 0000000000..b34cc7afbb --- /dev/null +++ b/test/server-functions/tsconfig.eslint.json @@ -0,0 +1,13 @@ +{ + // extend your base config to share compilerOptions, etc + //"extends": "./tsconfig.json", + "compilerOptions": { + // ensure that nobody can accidentally use this config for a build + "noEmit": true + }, + "include": [ + // whatever paths you intend to lint + "./**/*.ts", + "./**/*.tsx" + ] +} diff --git a/test/server-functions/tsconfig.json b/test/server-functions/tsconfig.json new file mode 100644 index 0000000000..e05dc9ac67 --- /dev/null +++ b/test/server-functions/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "paths": { + "@payload-config": ["./config.js"] + } + } +}