chore(examples): updates auth example to latest (#10090)

The auth example was still on `v3.0.0-beta.24`, was missing its users
collection config, and was not yet using the component paths pattern
established here: #7246. This updates to latest and fixes these issues.
This example can still use further improvements and housekeeping which
will come in future PRs.
This commit is contained in:
Jacob Fletcher
2024-12-19 23:54:24 -05:00
committed by GitHub
parent dd3c2eb42b
commit 7292220109
20 changed files with 4796 additions and 6465 deletions

View File

@@ -1,7 +1,11 @@
# NOTE: Change port of `PAYLOAD_PUBLIC_SITE_URL` if front-end is running on another server # Database connection string
PAYLOAD_PUBLIC_SITE_URL=http://localhost:3000 DATABASE_URI=mongodb://127.0.0.1/payload-draft-preview-example
PAYLOAD_PUBLIC_SERVER_URL=http://localhost:3000
# Used to encrypt JWT tokens
PAYLOAD_SECRET=YOUR_SECRET_HERE
# Used to configure CORS, format links and more. No trailing slash
NEXT_PUBLIC_SERVER_URL=http://localhost:3000 NEXT_PUBLIC_SERVER_URL=http://localhost:3000
DATABASE_URI=mongodb://127.0.0.1/payload-example-auth
PAYLOAD_SECRET=PAYLOAD_AUTH_EXAMPLE_SECRET_KEY # Used to share cookies across subdomains
COOKIE_DOMAIN=localhost COOKIE_DOMAIN=localhost

View File

@@ -1,12 +0,0 @@
.tmp
**/.git
**/.hg
**/.pnp.*
**/.svn
**/.yarn/**
**/build
**/dist/**
**/node_modules
**/temp
playwright.config.ts
jest.config.js

View File

@@ -1,15 +1,4 @@
module.exports = { module.exports = {
extends: ['plugin:@next/next/core-web-vitals', '@payloadcms'],
ignorePatterns: ['**/payload-types.ts'],
overrides: [
{
extends: ['plugin:@typescript-eslint/disable-type-checked'],
files: ['*.js', '*.cjs', '*.json', '*.md', '*.yml', '*.yaml'],
},
],
parserOptions: {
project: ['./tsconfig.json'],
tsconfigRootDir: __dirname,
},
root: true, root: true,
extends: ['@payloadcms'],
} }

View File

@@ -1,4 +1,5 @@
build build
dist dist
node_modules node_modules
package - lock.json.env package-lock.json
.env

24
examples/auth/.swcrc Normal file
View File

@@ -0,0 +1,24 @@
{
"$schema": "https://json.schemastore.org/swcrc",
"sourceMaps": true,
"jsc": {
"target": "esnext",
"parser": {
"syntax": "typescript",
"tsx": true,
"dts": true
},
"transform": {
"react": {
"runtime": "automatic",
"pragmaFrag": "React.Fragment",
"throwIfNamespace": true,
"development": false,
"useBuiltins": true
}
}
},
"module": {
"type": "es6"
}
}

View File

@@ -2,28 +2,27 @@
This [Payload Auth Example](https://github.com/payloadcms/payload/tree/main/examples/auth) demonstrates how to implement [Payload Authentication](https://payloadcms.com/docs/authentication/overview) into all types of applications. Follow the [Quick Start](#quick-start) to get up and running quickly. This [Payload Auth Example](https://github.com/payloadcms/payload/tree/main/examples/auth) demonstrates how to implement [Payload Authentication](https://payloadcms.com/docs/authentication/overview) into all types of applications. Follow the [Quick Start](#quick-start) to get up and running quickly.
**IMPORTANT—This example includes a fully integrated Next.js App Router front-end that runs on the same server as Payload.** If you are working on an application running on an entirely separate server, the principals are generally the same. To learn more about this, [check out how Payload can be used in its various headless capacities](https://payloadcms.com/blog/the-ultimate-guide-to-using-nextjs-with-payload).
## Quick Start ## Quick Start
To spin up this example locally, follow these steps: To spin up this example locally, follow the steps below:
1. Clone this repo 1. Clone this repo
1. `cd` into this directory and run `pnpm i --ignore-workspace`\*, `yarn`, or `npm install` 1. Navigate into the project directory and install dependencies using your preferred package manager:
> \*If you are running using pnpm within the Payload Monorepo, the `--ignore-workspace` flag is needed so that pnpm generates a lockfile in this example's directory despite the fact that one exists in root. - `pnpm i --ignore-workspace`\*, `yarn`, or `npm install`
1. `cp .env.example .env` to copy the example environment variables > \*NOTE: The --ignore-workspace flag is needed if you are running this example within the Payload monorepo to avoid workspace conflicts.
> Adjust `PAYLOAD_PUBLIC_SITE_URL` in the `.env` if your front-end is running on a separate domain or port. 1. Start the server:
- Depending on your package manager, run `pnpm dev`, `yarn dev` or `npm run dev`
- When prompted, type `y` then `enter` to seed the database with sample data
1. Access the application:
- Open your browser and navigate to `http://localhost:3000` to access the homepage.
- Open `http://localhost:3000/admin` to access the admin panel.
1. Login:
1. `pnpm dev`, `yarn dev` or `npm run dev` to start the server - Use the following credentials to log into the admin panel:
- Press `y` when prompted to seed the database > `Email: demo@payloadcms.com` > `Password: demo`
1. `open http://localhost:3000` to access the home page
1. `open http://localhost:3000/admin` to access the admin panel
- Login with email `demo@payloadcms.com` and password `demo`
That's it! Changes made in `./src` will be reflected in your app. See the [Development](#development) section for more details.
## How it works ## How it works

View File

@@ -2,4 +2,4 @@
/// <reference types="next/image-types/global" /> /// <reference types="next/image-types/global" />
// NOTE: This file should not be edited // NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information. // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

View File

@@ -3,41 +3,39 @@
"version": "1.0.0", "version": "1.0.0",
"description": "Payload authentication example.", "description": "Payload authentication example.",
"license": "MIT", "license": "MIT",
"type": "module",
"main": "dist/server.js", "main": "dist/server.js",
"scripts": { "scripts": {
"build": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts NODE_OPTIONS=--no-deprecation next build", "build": "cross-env NODE_OPTIONS=--no-deprecation next build",
"dev": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts && pnpm seed && cross-env NODE_OPTIONS=--no-deprecation next dev", "dev": "cross-env NODE_OPTIONS=--no-deprecation && pnpm seed && next dev",
"generate:graphQLSchema": "PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:graphQLSchema", "generate:importmap": "cross-env NODE_OPTIONS=--no-deprecation payload generate:importmap",
"generate:types": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:types", "generate:schema": "payload-graphql generate:schema",
"lint": "cross-env NODE_OPTIONS=--no-deprecation next lint", "generate:types": "cross-env NODE_OPTIONS=--no-deprecation payload generate:types",
"lint:fix": "eslint --fix --ext .ts,.tsx src", "payload": "cross-env NODE_OPTIONS=--no-deprecation payload",
"payload": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload",
"seed": "npm run payload migrate:fresh", "seed": "npm run payload migrate:fresh",
"start": "cross-env NODE_OPTIONS=--no-deprecation next start" "start": "cross-env NODE_OPTIONS=--no-deprecation next start"
}, },
"dependencies": { "dependencies": {
"@payloadcms/db-mongodb": "3.0.0-beta.24", "@payloadcms/db-mongodb": "latest",
"@payloadcms/next": "3.0.0-beta.24", "@payloadcms/next": "latest",
"@payloadcms/richtext-slate": "3.0.0-beta.24", "@payloadcms/richtext-slate": "latest",
"@payloadcms/ui": "3.0.0-beta.24", "@payloadcms/ui": "latest",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"next": "14.3.0-canary.68", "next": "^15.0.0",
"payload": "3.0.0-beta.24", "payload": "latest",
"react": "^18.2.0", "react": "19.0.0",
"react-dom": "^18.2.0", "react-dom": "19.0.0",
"react-hook-form": "^7.51.3" "react-hook-form": "^7.51.3"
}, },
"devDependencies": { "devDependencies": {
"@next/eslint-plugin-next": "^13.1.6", "@payloadcms/graphql": "latest",
"@payloadcms/eslint-config": "^1.1.1", "@swc/core": "^1.6.13",
"@swc/core": "^1.4.14", "@types/ejs": "^3.1.5",
"@swc/types": "^0.1.6", "@types/react": "19.0.1",
"@types/node": "^20.11.25", "@types/react-dom": "19.0.1",
"@types/react": "^18.2.64",
"@types/react-dom": "^18.2.21",
"dotenv": "^16.4.5",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"tsx": "^4.7.1", "eslint-config-next": "^15.0.0",
"typescript": "5.4.4" "tsx": "^4.16.2",
"typescript": "5.5.2"
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,7 @@ import Link from 'next/link'
import React from 'react' import React from 'react'
import { Gutter } from '../Gutter' import { Gutter } from '../Gutter'
import { HeaderNav } from './Nav'
import classes from './index.module.scss' import classes from './index.module.scss'
export const Header = () => { export const Header = () => {
@@ -23,6 +24,7 @@ export const Header = () => {
/> />
</picture> </picture>
</Link> </Link>
<HeaderNav />
</Gutter> </Gutter>
</header> </header>
) )

View File

@@ -5,18 +5,21 @@ import type { Metadata } from 'next'
import config from '@payload-config' import config from '@payload-config'
import { generatePageMetadata, NotFoundPage } from '@payloadcms/next/views' import { generatePageMetadata, NotFoundPage } from '@payloadcms/next/views'
import { importMap } from '../importMap.js'
type Args = { type Args = {
params: { params: Promise<{
segments: string[] segments: string[]
} }>
searchParams: { searchParams: Promise<{
[key: string]: string | string[] [key: string]: string | string[]
} }>
} }
export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> => export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>
generatePageMetadata({ config, params, searchParams }) generatePageMetadata({ config, params, searchParams })
const NotFound = ({ params, searchParams }: Args) => NotFoundPage({ config, params, searchParams }) const NotFound = ({ params, searchParams }: Args) =>
NotFoundPage({ config, importMap, params, searchParams })
export default NotFound export default NotFound

View File

@@ -5,18 +5,20 @@ import type { Metadata } from 'next'
import config from '@payload-config' import config from '@payload-config'
import { generatePageMetadata, RootPage } from '@payloadcms/next/views' import { generatePageMetadata, RootPage } from '@payloadcms/next/views'
type Args = { import { importMap } from '../importMap.js'
params: {
segments: string[]
}
searchParams: {
[key: string]: string | string[]
}
}
type Args = {
params: Promise<{
segments: string[]
}>
searchParams: Promise<{
[key: string]: string | string[]
}>
}
export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> => export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>
generatePageMetadata({ config, params, searchParams }) generatePageMetadata({ config, params, searchParams })
const Page = ({ params, searchParams }: Args) => RootPage({ config, params, searchParams }) const Page = ({ params, searchParams }: Args) =>
RootPage({ config, importMap, params, searchParams })
export default Page export default Page

View File

@@ -0,0 +1,5 @@
import { BeforeLogin as BeforeLogin_8a7ab0eb7ab5c511aba12e68480bfe5e } from '@/components/BeforeLogin'
export const importMap = {
'@/components/BeforeLogin#BeforeLogin': BeforeLogin_8a7ab0eb7ab5c511aba12e68480bfe5e,
}

View File

@@ -1,16 +1,32 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */ /* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import configPromise from '@payload-config' import type { ServerFunctionClient } from 'payload'
import '@payloadcms/next/css' import '@payloadcms/next/css'
import { RootLayout } from '@payloadcms/next/layouts' import config from '@payload-config'
import { handleServerFunctions, RootLayout } from '@payloadcms/next/layouts'
import React from 'react' import React from 'react'
import { importMap } from './admin/importMap.js'
import './custom.scss' import './custom.scss'
type Args = { type Args = {
children: React.ReactNode children: React.ReactNode
} }
const Layout = ({ children }: Args) => <RootLayout config={configPromise}>{children}</RootLayout> const serverFunction: ServerFunctionClient = async function (args) {
'use server'
return handleServerFunctions({
...args,
config,
importMap,
})
}
const Layout = ({ children }: Args) => (
<RootLayout config={config} importMap={importMap} serverFunction={serverFunction}>
{children}
</RootLayout>
)
export default Layout export default Layout

View File

@@ -1,13 +1,62 @@
import type { CollectionConfig } from 'payload/types' import type { CollectionConfig } from 'payload/types'
import { admins } from './access/admins'
import adminsAndUser from './access/adminsAndUser'
import { anyone } from './access/anyone'
import { checkRole } from './access/checkRole'
import { loginAfterCreate } from './hooks/loginAfterCreate'
import { protectRoles } from './hooks/protectRoles'
export const Users: CollectionConfig = { export const Users: CollectionConfig = {
slug: 'users', slug: 'users',
auth: {
tokenExpiration: 28800, // 8 hours
cookies: {
sameSite: 'none',
secure: true,
domain: process.env.COOKIE_DOMAIN,
},
},
admin: { admin: {
useAsTitle: 'email', useAsTitle: 'email',
}, },
auth: true, access: {
read: adminsAndUser,
create: anyone,
update: adminsAndUser,
delete: admins,
admin: ({ req: { user } }) => checkRole(['admin'], user),
},
hooks: {
afterChange: [loginAfterCreate],
},
fields: [ fields: [
// Email added by default {
// Add more fields as needed name: 'firstName',
type: 'text',
},
{
name: 'lastName',
type: 'text',
},
{
name: 'roles',
type: 'select',
hasMany: true,
saveToJWT: true,
hooks: {
beforeChange: [protectRoles],
},
options: [
{
label: 'Admin',
value: 'admin',
},
{
label: 'User',
value: 'user',
},
],
},
], ],
} }

View File

@@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
const BeforeLogin: React.FC = () => { export const BeforeLogin: React.FC = () => {
if (process.env.PAYLOAD_PUBLIC_SEED === 'true') { if (process.env.PAYLOAD_PUBLIC_SEED === 'true') {
return ( return (
<p> <p>
@@ -13,5 +13,3 @@ const BeforeLogin: React.FC = () => {
} }
return null return null
} }
export default BeforeLogin

View File

@@ -7,16 +7,53 @@
*/ */
export interface Config { export interface Config {
auth: {
users: UserAuthOperations;
};
collections: { collections: {
users: User; users: User;
'payload-locked-documents': PayloadLockedDocument;
'payload-preferences': PayloadPreference; 'payload-preferences': PayloadPreference;
'payload-migrations': PayloadMigration; 'payload-migrations': PayloadMigration;
}; };
collectionsJoins: {};
collectionsSelect: {
users: UsersSelect<false> | UsersSelect<true>;
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
};
db: {
defaultIDType: string;
};
globals: {}; globals: {};
globalsSelect: {};
locale: null; locale: null;
user: User & { user: User & {
collection: 'users'; 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 * This interface was referenced by `Config`'s JSON-Schema
@@ -38,6 +75,24 @@ export interface User {
lockUntil?: string | null; lockUntil?: string | null;
password?: 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 * This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-preferences". * via the `definition` "payload-preferences".
@@ -72,6 +127,63 @@ export interface PayloadMigration {
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "users_select".
*/
export interface UsersSelect<T extends boolean = true> {
firstName?: T;
lastName?: T;
roles?: T;
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<T extends boolean = true> {
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<T extends boolean = true> {
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<T extends boolean = true> {
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' { declare module 'payload' {

View File

@@ -2,28 +2,21 @@ import { mongooseAdapter } from '@payloadcms/db-mongodb'
import { slateEditor } from '@payloadcms/richtext-slate' import { slateEditor } from '@payloadcms/richtext-slate'
import { fileURLToPath } from 'node:url' import { fileURLToPath } from 'node:url'
import path from 'path' import path from 'path'
import { buildConfig } from 'payload/config' import { buildConfig } from 'payload'
import { Users } from './collections/Users' import { Users } from './collections/Users'
import BeforeLogin from './components/BeforeLogin'
const filename = fileURLToPath(import.meta.url) const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename) const dirname = path.dirname(filename)
export default buildConfig({ export default buildConfig({
admin: { admin: {
components: { components: {
beforeLogin: [BeforeLogin], beforeLogin: ['@/components/BeforeLogin#BeforeLogin'],
}, },
}, },
collections: [Users], collections: [Users],
cors: [ cors: [process.env.NEXT_PUBLIC_SERVER_URL || ''].filter(Boolean),
process.env.PAYLOAD_PUBLIC_SERVER_URL || '', csrf: [process.env.NEXT_PUBLIC_SERVER_URL || ''].filter(Boolean),
process.env.PAYLOAD_PUBLIC_SITE_URL || '',
].filter(Boolean),
csrf: [
process.env.PAYLOAD_PUBLIC_SERVER_URL || '',
process.env.PAYLOAD_PUBLIC_SITE_URL || '',
].filter(Boolean),
db: mongooseAdapter({ db: mongooseAdapter({
url: process.env.DATABASE_URI || '', url: process.env.DATABASE_URI || '',
}), }),

View File

@@ -23,10 +23,25 @@
} }
], ],
"paths": { "paths": {
"@/*": ["./src/*"], "@/*": [
"@payload-config": ["src/payload.config.ts"] "./src/*"
} ],
"@payload-config": [
"src/payload.config.ts"
],
"@payload-types": [
"src/payload-types.ts"
]
}, },
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "target": "ES2022",
"exclude": ["node_modules"] },
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": [
"node_modules"
]
} }

View File

@@ -1,8 +1,8 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import type { ServerFunctionClient } from 'payload' import type { ServerFunctionClient } from 'payload'
import '@payloadcms/next/css' import '@payloadcms/next/css'
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import config from '@payload-config' import config from '@payload-config'
import { handleServerFunctions, RootLayout } from '@payloadcms/next/layouts' import { handleServerFunctions, RootLayout } from '@payloadcms/next/layouts'
import React from 'react' import React from 'react'