From 0857dbe4650da8fec6e5ca7644a9fcee7a5e1a8b Mon Sep 17 00:00:00 2001 From: Elliot DeNolf Date: Wed, 24 Apr 2024 14:36:08 -0400 Subject: [PATCH 1/2] Revert "fix: issues creating the first user (#5986)" This reverts commit 0ede95f3752ca931a5ad10483ca8ae0a80ccc3a2. --- .../views/CreateFirstUser/index.client.tsx | 6 +-- .../next/src/views/CreateFirstUser/index.tsx | 33 ++++++------ .../src/views/Edit/Default/Auth/index.tsx | 2 +- .../next/src/views/ResetPassword/index.tsx | 2 +- .../ui/src/fields/ConfirmPassword/index.tsx | 46 +++++------------ packages/ui/src/fields/Email/index.tsx | 2 +- test/auth/config.ts | 46 ++++++++--------- test/auth/e2e.spec.ts | 50 +++++-------------- test/fields-relationship/e2e.spec.ts | 4 +- 9 files changed, 68 insertions(+), 123 deletions(-) diff --git a/packages/next/src/views/CreateFirstUser/index.client.tsx b/packages/next/src/views/CreateFirstUser/index.client.tsx index 23d564633e..4abe636cd6 100644 --- a/packages/next/src/views/CreateFirstUser/index.client.tsx +++ b/packages/next/src/views/CreateFirstUser/index.client.tsx @@ -6,16 +6,16 @@ import { useComponentMap } from '@payloadcms/ui/providers/ComponentMap' import React from 'react' export const CreateFirstUserFields: React.FC<{ - baseAuthFieldMap: FieldMap + createFirstUserFieldMap: FieldMap userSlug: string -}> = ({ baseAuthFieldMap, userSlug }) => { +}> = ({ createFirstUserFieldMap, userSlug }) => { const { getFieldMap } = useComponentMap() const fieldMap = getFieldMap({ collectionSlug: userSlug }) return ( = async ({ initPageRe }, } = initPageResult - const baseAuthFields: Field[] = [ + const fields: Field[] = [ { name: 'email', type: 'email', @@ -52,29 +52,16 @@ export const CreateFirstUserView: React.FC = async ({ initPageRe }, ] - const ssrAuthFields = [...baseAuthFields] - const WithServerSideProps: WithServerSidePropsType = ({ Component, ...rest }) => { return } - const userFieldSchema = config.collections.find((c) => c.slug === userSlug) - ssrAuthFields.push(...userFieldSchema.fields) - - const formState = await buildStateFromSchema({ - fieldSchema: ssrAuthFields, - operation: 'create', - preferences: { - fields: undefined - }, - req, - }) - - const baseAuthFieldMap = mapFields({ + const createFirstUserFieldMap = mapFields({ WithServerSideProps, config, - fieldSchema: baseAuthFields, + fieldSchema: fields, i18n, + parentPath: userSlug, }).map((field) => { // Transform field types for the password and confirm-password fields if (field.name === 'password') { @@ -96,6 +83,13 @@ export const CreateFirstUserView: React.FC = async ({ initPageRe return field }) + const formState = await buildStateFromSchema({ + fieldSchema: fields, + operation: 'create', + preferences: { fields: {} }, + req, + }) + return (

{req.t('general:welcome')}

@@ -107,7 +101,10 @@ export const CreateFirstUserView: React.FC = async ({ initPageRe redirect={adminRoute} validationOperation="create" > - + {req.t('general:create')}
diff --git a/packages/next/src/views/Edit/Default/Auth/index.tsx b/packages/next/src/views/Edit/Default/Auth/index.tsx index 03bbeb42c6..a7a69edc8b 100644 --- a/packages/next/src/views/Edit/Default/Auth/index.tsx +++ b/packages/next/src/views/Edit/Default/Auth/index.tsx @@ -105,7 +105,7 @@ export const Auth: React.FC = (props) => { name="password" required /> - + )} diff --git a/packages/next/src/views/ResetPassword/index.tsx b/packages/next/src/views/ResetPassword/index.tsx index e68afd257d..ebe5593d1c 100644 --- a/packages/next/src/views/ResetPassword/index.tsx +++ b/packages/next/src/views/ResetPassword/index.tsx @@ -85,7 +85,7 @@ export const ResetPassword: React.FC = ({ initPageResult, params name="password" required /> - + {i18n.t('authentication:resetPassword')} diff --git a/packages/ui/src/fields/ConfirmPassword/index.tsx b/packages/ui/src/fields/ConfirmPassword/index.tsx index 85fd43dd9a..4eebd8069e 100644 --- a/packages/ui/src/fields/ConfirmPassword/index.tsx +++ b/packages/ui/src/fields/ConfirmPassword/index.tsx @@ -1,10 +1,8 @@ 'use client' -import type { Description, FormField, Validate } from 'payload/types' +import type { FormField } from 'payload/types' import React, { useCallback } from 'react' -import type { FormFieldBase } from '../shared/index.js' - import { FieldError } from '../../forms/FieldError/index.js' import { FieldLabel } from '../../forms/FieldLabel/index.js' import { useFormFields } from '../../forms/Form/context.js' @@ -12,36 +10,13 @@ import { useField } from '../../forms/useField/index.js' import { useTranslation } from '../../providers/Translation/index.js' import { fieldBaseClass } from '../shared/index.js' import './index.scss' -export type ConfirmPasswordFieldProps = FormFieldBase & { - autoComplete?: string - className?: string - description?: Description + +export type ConfirmPasswordFieldProps = { disabled?: boolean - label?: string - name: string - path?: string - required?: boolean - style?: React.CSSProperties - validate?: Validate - width?: string } export const ConfirmPassword: React.FC = (props) => { - const { - name, - CustomError, - CustomLabel, - autoComplete, - className, - disabled, - errorProps, - label, - labelProps, - path: pathFromProps, - required, - style, - width, - } = props + const { disabled } = props const password = useFormFields(([fields]) => fields.password) const { t } = useTranslation() @@ -61,8 +36,11 @@ export const ConfirmPassword: React.FC = (props) => { [password, t], ) - const { formProcessing, path, setValue, showError, value } = useField({ - path: pathFromProps || name, + const path = 'confirm-password' + + const { setValue, showError, value } = useField({ + disableFormData: true, + path, validate, }) @@ -80,9 +58,9 @@ export const ConfirmPassword: React.FC = (props) => { /> = (props) => { const { path, setValue, showError, value } = useField({ path: pathFromContext || pathFromProps || name, - validate: typeof validate === 'function' ? memoizedValidate : undefined, + validate: memoizedValidate, }) return ( diff --git a/test/auth/config.ts b/test/auth/config.ts index 15bed3b8a6..42dba2fc0f 100644 --- a/test/auth/config.ts +++ b/test/auth/config.ts @@ -216,31 +216,29 @@ export default buildConfigWithDefaults({ }, ], onInit: async (payload) => { - if (process.env.SKIP_ON_INIT !== 'true') { - await payload.create({ - collection: 'users', - data: { - custom: 'Hello, world!', - email: devUser.email, - password: devUser.password, - }, - }) + await payload.create({ + collection: 'users', + data: { + custom: 'Hello, world!', + email: devUser.email, + password: devUser.password, + }, + }) - await payload.create({ - collection: 'api-keys', - data: { - apiKey: uuid(), - enableAPIKey: true, - }, - }) + await payload.create({ + collection: 'api-keys', + data: { + apiKey: uuid(), + enableAPIKey: true, + }, + }) - await payload.create({ - collection: 'api-keys', - data: { - apiKey: uuid(), - enableAPIKey: true, - }, - }) - } + await payload.create({ + collection: 'api-keys', + data: { + apiKey: uuid(), + enableAPIKey: true, + }, + }) }, }) diff --git a/test/auth/e2e.spec.ts b/test/auth/e2e.spec.ts index 17e7c5655f..8d006fd2d7 100644 --- a/test/auth/e2e.spec.ts +++ b/test/auth/e2e.spec.ts @@ -1,16 +1,13 @@ import type { Page } from '@playwright/test' import { expect, test } from '@playwright/test' -import { devUser } from 'credentials.js' import path from 'path' -import { wait } from 'payload/utilities' import { fileURLToPath } from 'url' -import { v4 as uuid } from 'uuid' import type { PayloadTestSDK } from '../helpers/sdk/index.js' import type { Config } from './payload-types.js' -import { initPageConsoleErrorCatch, saveDocAndAssert } from '../helpers.js' +import { initPageConsoleErrorCatch, login, saveDocAndAssert } from '../helpers.js' import { AdminUrlUtil } from '../helpers/adminUrlUtil.js' import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js' import { POLL_TOPASS_TIMEOUT } from '../playwright.config.js' @@ -21,37 +18,25 @@ const dirname = path.dirname(filename) let payload: PayloadTestSDK +/** + * TODO: Auth + * create first user + * unlock + * log out + */ + const { beforeAll, describe } = test const headers = { 'Content-Type': 'application/json', } -const createFirstUser = async ({ page, serverURL }: { page: Page; serverURL: string }) => { - await page.goto(serverURL + '/admin/create-first-user') - await page.locator('#field-email').fill(devUser.email) - await page.locator('#field-password').fill(devUser.password) - await page.locator('#field-confirm-password').fill(devUser.password) - await page.locator('#field-custom').fill('Hello, world!') - - await wait(500) - - await page.locator('.form-submit > button').click() - - await expect - .poll(() => page.url(), { timeout: POLL_TOPASS_TIMEOUT }) - .not.toContain('create-first-user') -} - describe('auth', () => { let page: Page let url: AdminUrlUtil let serverURL: string let apiURL: string - // Allows for testing create-first-user - process.env.SKIP_ON_INIT = 'true' - beforeAll(async ({ browser }) => { ;({ payload, serverURL } = await initPayloadE2ENoConfig({ dirname })) apiURL = `${serverURL}/api` @@ -61,22 +46,11 @@ describe('auth', () => { page = await context.newPage() initPageConsoleErrorCatch(page) - await createFirstUser({ page, serverURL }) + //await delayNetwork({ context, page, delay: 'Slow 4G' }) - await payload.create({ - collection: 'api-keys', - data: { - apiKey: uuid(), - enableAPIKey: true, - }, - }) - - await payload.create({ - collection: 'api-keys', - data: { - apiKey: uuid(), - enableAPIKey: true, - }, + await login({ + page, + serverURL, }) }) diff --git a/test/fields-relationship/e2e.spec.ts b/test/fields-relationship/e2e.spec.ts index 7e111ecc97..6260c934f5 100644 --- a/test/fields-relationship/e2e.spec.ts +++ b/test/fields-relationship/e2e.spec.ts @@ -383,11 +383,9 @@ describe('fields - relationship', () => { }) await page.goto(url.create) - // wait for relationship options to load - const relationFilterOptionsReq = page.waitForResponse(/api\/relation-filter-true/) + // select relationshipMany field that relies on siblingData field above await page.locator('#field-relationshipManyFiltered .rs__control').click() - await relationFilterOptionsReq const options = page.locator('#field-relationshipManyFiltered .rs__menu') await expect(options).toContainText('truth') From d40a734080fe58dcff7113e035fe8ed581da3568 Mon Sep 17 00:00:00 2001 From: Elliot DeNolf Date: Wed, 24 Apr 2024 15:17:13 -0400 Subject: [PATCH 2/2] wip: create first user fix --- .../src/routes/rest/auth/registerFirstUser.ts | 10 --- .../next/src/routes/rest/buildFormState.ts | 15 +++- .../views/CreateFirstUser/index.client.tsx | 71 ++++++++++++++++--- .../next/src/views/CreateFirstUser/index.tsx | 63 ++-------------- 4 files changed, 80 insertions(+), 79 deletions(-) diff --git a/packages/next/src/routes/rest/auth/registerFirstUser.ts b/packages/next/src/routes/rest/auth/registerFirstUser.ts index fdb6c41b04..93e113bf6b 100644 --- a/packages/next/src/routes/rest/auth/registerFirstUser.ts +++ b/packages/next/src/routes/rest/auth/registerFirstUser.ts @@ -1,6 +1,5 @@ import httpStatus from 'http-status' import { generatePayloadCookie } from 'payload/auth' -import { ValidationError } from 'payload/errors' import { registerFirstUserOperation } from 'payload/operations' import type { CollectionRouteHandler } from '../types.js' @@ -10,15 +9,6 @@ import { headersWithCors } from '../../../utilities/headersWithCors.js' export const registerFirstUser: CollectionRouteHandler = async ({ collection, req }) => { const { data, t } = req - if (data?.password !== data['confirm-password']) { - throw new ValidationError([ - { - field: 'confirm-password', - message: req.t('Password and confirm password fields do not match.'), - }, - ]) - } - const result = await registerFirstUserOperation({ collection, data: { diff --git a/packages/next/src/routes/rest/buildFormState.ts b/packages/next/src/routes/rest/buildFormState.ts index bd1a4c59a7..d88260277e 100644 --- a/packages/next/src/routes/rest/buildFormState.ts +++ b/packages/next/src/routes/rest/buildFormState.ts @@ -62,10 +62,19 @@ export const buildFormState = async ({ req }: { req: PayloadRequest }) => { }) } } else { - return Response.json(null, { - headers, - status: httpStatus.UNAUTHORIZED, + 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) { + return Response.json(null, { + headers, + status: httpStatus.UNAUTHORIZED, + }) + } } const fieldSchemaMap = getFieldSchemaMap(req) diff --git a/packages/next/src/views/CreateFirstUser/index.client.tsx b/packages/next/src/views/CreateFirstUser/index.client.tsx index 4abe636cd6..7764318174 100644 --- a/packages/next/src/views/CreateFirstUser/index.client.tsx +++ b/packages/next/src/views/CreateFirstUser/index.client.tsx @@ -1,25 +1,76 @@ 'use client' +import type { FormProps } from '@payloadcms/ui/forms/Form' import type { FieldMap } from '@payloadcms/ui/utilities/buildComponentMap' +import type { FormState } from 'payload/types' +import { ConfirmPassword } from '@payloadcms/ui/fields/ConfirmPassword' +import { Email } from '@payloadcms/ui/fields/Email' +import { Password } from '@payloadcms/ui/fields/Password' +import { Form } from '@payloadcms/ui/forms/Form' import { RenderFields } from '@payloadcms/ui/forms/RenderFields' +import { FormSubmit } from '@payloadcms/ui/forms/Submit' import { useComponentMap } from '@payloadcms/ui/providers/ComponentMap' +import { useConfig } from '@payloadcms/ui/providers/Config' +import { useTranslation } from '@payloadcms/ui/providers/Translation' +import { getFormState } from '@payloadcms/ui/utilities/getFormState' import React from 'react' -export const CreateFirstUserFields: React.FC<{ - createFirstUserFieldMap: FieldMap +export const CreateFirstUserClient: React.FC<{ + initialState: FormState userSlug: string -}> = ({ createFirstUserFieldMap, userSlug }) => { +}> = ({ initialState, userSlug }) => { const { getFieldMap } = useComponentMap() + const { + routes: { admin, api: apiRoute }, + serverURL, + } = useConfig() + + const { t } = useTranslation() + const fieldMap = getFieldMap({ collectionSlug: userSlug }) + const onChange: FormProps['onChange'][0] = React.useCallback( + async ({ formState: prevFormState }) => { + return getFormState({ + apiRoute, + body: { + collectionSlug: userSlug, + formState: prevFormState, + operation: 'create', + schemaPath: userSlug, + }, + serverURL, + }) + }, + [apiRoute, userSlug, serverURL], + ) + return ( - +
+ + + + + {t('general:create')} + ) } diff --git a/packages/next/src/views/CreateFirstUser/index.tsx b/packages/next/src/views/CreateFirstUser/index.tsx index a6454b5c2f..aff14a7a2e 100644 --- a/packages/next/src/views/CreateFirstUser/index.tsx +++ b/packages/next/src/views/CreateFirstUser/index.tsx @@ -1,15 +1,14 @@ -import type { FieldTypes } from 'payload/config' -import type { Field, WithServerSideProps as WithServerSidePropsType } from 'payload/types' -import type { AdminViewProps } from 'payload/types' +import type { + AdminViewProps, + Field, + WithServerSideProps as WithServerSidePropsType, +} from 'payload/types' -import { Form } from '@payloadcms/ui/forms/Form' -import { FormSubmit } from '@payloadcms/ui/forms/Submit' import { buildStateFromSchema } from '@payloadcms/ui/forms/buildStateFromSchema' import { WithServerSideProps as WithServerSidePropsGeneric } from '@payloadcms/ui/providers/ComponentMap' -import { mapFields } from '@payloadcms/ui/utilities/buildComponentMap' import React from 'react' -import { CreateFirstUserFields } from './index.client.js' +import { CreateFirstUserClient } from './index.client.js' import './index.scss' export { generateCreateFirstUserMetadata } from './meta.js' @@ -18,14 +17,9 @@ export const CreateFirstUserView: React.FC = async ({ initPageRe const { req, req: { - i18n, - payload, payload: { - config, config: { admin: { user: userSlug }, - routes: { admin: adminRoute, api: apiRoute }, - serverURL, }, }, }, @@ -52,37 +46,6 @@ export const CreateFirstUserView: React.FC = async ({ initPageRe }, ] - const WithServerSideProps: WithServerSidePropsType = ({ Component, ...rest }) => { - return - } - - const createFirstUserFieldMap = mapFields({ - WithServerSideProps, - config, - fieldSchema: fields, - i18n, - parentPath: userSlug, - }).map((field) => { - // Transform field types for the password and confirm-password fields - if (field.name === 'password') { - const type: keyof FieldTypes = 'password' - - return { - ...field, - type, - } - } - if (field.name === 'confirm-password') { - const type: keyof FieldTypes = 'confirmPassword' - - return { - ...field, - type, - } - } - return field - }) - const formState = await buildStateFromSchema({ fieldSchema: fields, operation: 'create', @@ -94,19 +57,7 @@ export const CreateFirstUserView: React.FC = async ({ initPageRe

{req.t('general:welcome')}

{req.t('authentication:beginCreateFirstUser')}

-
- - {req.t('general:create')} - +
) }