fix: validates password and confirm password on the server (#7410)
Fixes https://github.com/payloadcms/payload/issues/7380 Adjusts how the password/confirm-password fields are validated. Moves validation to the server, adds them to a custom schema under the schema path `${collectionSlug}.auth` for auth enabled collections.
This commit is contained in:
@@ -31,6 +31,9 @@ export interface Config {
|
||||
'payload-preferences': PayloadPreference;
|
||||
'payload-migrations': PayloadMigration;
|
||||
};
|
||||
db: {
|
||||
defaultIDType: string;
|
||||
};
|
||||
globals: {
|
||||
settings: Setting;
|
||||
test: Test;
|
||||
@@ -52,26 +55,32 @@ export interface UserAuthOperations {
|
||||
email: string;
|
||||
};
|
||||
login: {
|
||||
password: string;
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
registerFirstUser: {
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
unlock: {
|
||||
email: string;
|
||||
};
|
||||
}
|
||||
export interface NonAdminUserAuthOperations {
|
||||
forgotPassword: {
|
||||
email: string;
|
||||
};
|
||||
login: {
|
||||
password: string;
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
registerFirstUser: {
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
unlock: {
|
||||
email: string;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
|
||||
@@ -13,6 +13,7 @@ import type { Config } from './payload-types.js'
|
||||
|
||||
import {
|
||||
ensureCompilationIsDone,
|
||||
exactText,
|
||||
getRoutes,
|
||||
initPageConsoleErrorCatch,
|
||||
saveDocAndAssert,
|
||||
@@ -34,8 +35,6 @@ const headers = {
|
||||
}
|
||||
|
||||
const createFirstUser = async ({
|
||||
customAdminRoutes,
|
||||
customRoutes,
|
||||
page,
|
||||
serverURL,
|
||||
}: {
|
||||
@@ -49,19 +48,35 @@ const createFirstUser = async ({
|
||||
routes: { createFirstUser: createFirstUserRoute },
|
||||
},
|
||||
routes: { admin: adminRoute },
|
||||
} = getRoutes({
|
||||
customAdminRoutes,
|
||||
customRoutes,
|
||||
})
|
||||
} = getRoutes({})
|
||||
|
||||
// wait for create first user route
|
||||
await page.goto(serverURL + `${adminRoute}${createFirstUserRoute}`)
|
||||
|
||||
// forget to fill out confirm password
|
||||
await page.locator('#field-email').fill(devUser.email)
|
||||
await page.locator('#field-password').fill(devUser.password)
|
||||
await wait(500)
|
||||
await page.locator('.form-submit > button').click()
|
||||
await expect(page.locator('.field-type.confirm-password .field-error')).toHaveText(
|
||||
'This field is required.',
|
||||
)
|
||||
|
||||
// make them match, but does not pass password validation
|
||||
await page.locator('#field-email').fill(devUser.email)
|
||||
await page.locator('#field-password').fill('12')
|
||||
await page.locator('#field-confirm-password').fill('12')
|
||||
await wait(500)
|
||||
await page.locator('.form-submit > button').click()
|
||||
await expect(page.locator('.field-type.password .field-error')).toHaveText(
|
||||
'This value must be longer than the minimum length of 3 characters.',
|
||||
)
|
||||
|
||||
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
|
||||
@@ -109,7 +124,7 @@ describe('auth', () => {
|
||||
})
|
||||
|
||||
describe('authenticated users', () => {
|
||||
beforeAll(({ browser }) => {
|
||||
beforeAll(() => {
|
||||
url = new AdminUrlUtil(serverURL, slug)
|
||||
})
|
||||
|
||||
@@ -118,15 +133,35 @@ describe('auth', () => {
|
||||
const emailBeforeSave = await page.locator('#field-email').inputValue()
|
||||
await page.locator('#change-password').click()
|
||||
await page.locator('#field-password').fill('password')
|
||||
// should fail to save without confirm password
|
||||
await page.locator('#action-save').click()
|
||||
await expect(
|
||||
page.locator('.field-type.confirm-password .tooltip--show', {
|
||||
hasText: exactText('This field is required.'),
|
||||
}),
|
||||
).toBeVisible()
|
||||
|
||||
// should fail to save with incorrect confirm password
|
||||
await page.locator('#field-confirm-password').fill('wrong password')
|
||||
await page.locator('#action-save').click()
|
||||
await expect(
|
||||
page.locator('.field-type.confirm-password .tooltip--show', {
|
||||
hasText: exactText('Passwords do not match.'),
|
||||
}),
|
||||
).toBeVisible()
|
||||
|
||||
// should succeed with matching confirm password
|
||||
await page.locator('#field-confirm-password').fill('password')
|
||||
await saveDocAndAssert(page)
|
||||
await saveDocAndAssert(page, '#action-save')
|
||||
|
||||
// should still have the same email
|
||||
await expect(page.locator('#field-email')).toHaveValue(emailBeforeSave)
|
||||
})
|
||||
|
||||
test('should have up-to-date user in `useAuth` hook', async () => {
|
||||
await page.goto(url.account)
|
||||
await page.waitForURL(url.account)
|
||||
await expect(page.locator('#users-api-result')).toHaveText('Hello, world!')
|
||||
await expect(page.locator('#users-api-result')).toHaveText('')
|
||||
await expect(page.locator('#use-auth-result')).toHaveText('Hello, world!')
|
||||
const field = page.locator('#field-custom')
|
||||
await field.fill('Goodbye, world!')
|
||||
|
||||
@@ -20,6 +20,9 @@ export interface Config {
|
||||
'payload-preferences': PayloadPreference;
|
||||
'payload-migrations': PayloadMigration;
|
||||
};
|
||||
db: {
|
||||
defaultIDType: string;
|
||||
};
|
||||
globals: {};
|
||||
locale: null;
|
||||
user:
|
||||
@@ -38,39 +41,48 @@ export interface UserAuthOperations {
|
||||
email: string;
|
||||
};
|
||||
login: {
|
||||
password: string;
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
registerFirstUser: {
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
unlock: {
|
||||
email: string;
|
||||
};
|
||||
}
|
||||
export interface ApiKeyAuthOperations {
|
||||
forgotPassword: {
|
||||
email: string;
|
||||
};
|
||||
login: {
|
||||
password: string;
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
registerFirstUser: {
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
unlock: {
|
||||
email: string;
|
||||
};
|
||||
}
|
||||
export interface PublicUserAuthOperations {
|
||||
forgotPassword: {
|
||||
email: string;
|
||||
};
|
||||
login: {
|
||||
password: string;
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
registerFirstUser: {
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
unlock: {
|
||||
email: string;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
@@ -207,6 +219,6 @@ export interface Auth {
|
||||
|
||||
|
||||
declare module 'payload' {
|
||||
// @ts-ignore
|
||||
// @ts-ignore
|
||||
export interface GeneratedTypes extends Config {}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import type { Payload } from 'payload'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import path from 'path'
|
||||
import { getFileByPath, mapAsync } from 'payload'
|
||||
import { wait } from 'payload/shared'
|
||||
|
||||
import type { NextRESTClient } from '../helpers/NextRESTClient.js'
|
||||
import type { Post } from './payload-types.js'
|
||||
@@ -634,17 +635,16 @@ describe('collections-graphql', () => {
|
||||
|
||||
it('should sort find results by nearest distance', async () => {
|
||||
// creating twice as many records as we are querying to get a random sample
|
||||
await mapAsync([...Array(10)], () => {
|
||||
// setTimeout used to randomize the creation timestamp
|
||||
setTimeout(async () => {
|
||||
await payload.create({
|
||||
collection: pointSlug,
|
||||
data: {
|
||||
// only randomize longitude to make distance comparison easy
|
||||
point: [Math.random(), 0],
|
||||
},
|
||||
})
|
||||
}, Math.random())
|
||||
await mapAsync([...Array(10)], async () => {
|
||||
// randomize the creation timestamp
|
||||
await wait(Math.random())
|
||||
await payload.create({
|
||||
collection: pointSlug,
|
||||
data: {
|
||||
// only randomize longitude to make distance comparison easy
|
||||
point: [Math.random(), 0],
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
const nearQuery = `
|
||||
@@ -1185,7 +1185,7 @@ describe('collections-graphql', () => {
|
||||
expect(errors[0].message).toEqual('The following field is invalid: password')
|
||||
expect(errors[0].path[0]).toEqual('test2')
|
||||
expect(errors[0].extensions.name).toEqual('ValidationError')
|
||||
expect(errors[0].extensions.data.errors[0].message).toEqual('No password was given')
|
||||
expect(errors[0].extensions.data.errors[0].message).toEqual('This field is required.')
|
||||
expect(errors[0].extensions.data.errors[0].field).toEqual('password')
|
||||
|
||||
expect(Array.isArray(errors[1].locations)).toEqual(true)
|
||||
|
||||
Reference in New Issue
Block a user