fix: allows for emails to be non unique when allowEmailLogin is false (#9541)
### What? When you prevent users from authenticating with their email, we should not enforce uniqueness on the email field. ### Why? We never set the unique property to false. ### How? Set the unique property to false if `loginWithUsername.allowEmailLogin` is `false`.
This commit is contained in:
@@ -14,7 +14,7 @@ import {
|
|||||||
GraphQLString,
|
GraphQLString,
|
||||||
} from 'graphql'
|
} from 'graphql'
|
||||||
import { buildVersionCollectionFields, flattenTopLevelFields, formatNames, toWords } from 'payload'
|
import { buildVersionCollectionFields, flattenTopLevelFields, formatNames, toWords } from 'payload'
|
||||||
import { fieldAffectsData } from 'payload/shared'
|
import { fieldAffectsData, getLoginOptions } from 'payload/shared'
|
||||||
|
|
||||||
import type { ObjectTypeConfig } from './buildObjectType.js'
|
import type { ObjectTypeConfig } from './buildObjectType.js'
|
||||||
|
|
||||||
@@ -442,10 +442,9 @@ export function initCollections({ config, graphqlResult }: InitCollectionsGraphQ
|
|||||||
if (!collectionConfig.auth.disableLocalStrategy) {
|
if (!collectionConfig.auth.disableLocalStrategy) {
|
||||||
const authArgs = {}
|
const authArgs = {}
|
||||||
|
|
||||||
const canLoginWithEmail =
|
const { canLoginWithEmail, canLoginWithUsername } = getLoginOptions(
|
||||||
!collectionConfig.auth.loginWithUsername ||
|
collectionConfig.auth.loginWithUsername,
|
||||||
collectionConfig.auth.loginWithUsername?.allowEmailLogin
|
)
|
||||||
const canLoginWithUsername = collectionConfig.auth.loginWithUsername
|
|
||||||
|
|
||||||
if (canLoginWithEmail) {
|
if (canLoginWithEmail) {
|
||||||
authArgs['email'] = { type: new GraphQLNonNull(GraphQLString) }
|
authArgs['email'] = { type: new GraphQLNonNull(GraphQLString) }
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import type { FormState } from 'payload'
|
|||||||
|
|
||||||
import { Form, FormSubmit, PasswordField, useAuth, useConfig, useTranslation } from '@payloadcms/ui'
|
import { Form, FormSubmit, PasswordField, useAuth, useConfig, useTranslation } from '@payloadcms/ui'
|
||||||
import { formatAdminURL } from '@payloadcms/ui/shared'
|
import { formatAdminURL } from '@payloadcms/ui/shared'
|
||||||
|
import { getLoginOptions } from 'payload/shared'
|
||||||
|
|
||||||
import type { LoginFieldProps } from '../LoginField/index.js'
|
import type { LoginFieldProps } from '../LoginField/index.js'
|
||||||
|
|
||||||
@@ -36,9 +37,7 @@ export const LoginForm: React.FC<{
|
|||||||
const collectionConfig = config.collections?.find((collection) => collection?.slug === userSlug)
|
const collectionConfig = config.collections?.find((collection) => collection?.slug === userSlug)
|
||||||
const { auth: authOptions } = collectionConfig
|
const { auth: authOptions } = collectionConfig
|
||||||
const loginWithUsername = authOptions.loginWithUsername
|
const loginWithUsername = authOptions.loginWithUsername
|
||||||
const canLoginWithEmail =
|
const { canLoginWithEmail, canLoginWithUsername } = getLoginOptions(loginWithUsername)
|
||||||
!authOptions.loginWithUsername || authOptions.loginWithUsername.allowEmailLogin
|
|
||||||
const canLoginWithUsername = authOptions.loginWithUsername
|
|
||||||
|
|
||||||
const [loginType] = React.useState<LoginFieldProps['type']>(() => {
|
const [loginType] = React.useState<LoginFieldProps['type']>(() => {
|
||||||
if (canLoginWithEmail && canLoginWithUsername) {
|
if (canLoginWithEmail && canLoginWithUsername) {
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ export const getBaseAuthFields = (authConfig: IncomingAuthType): Field[] => {
|
|||||||
if (authConfig.loginWithUsername.requireUsername === false) {
|
if (authConfig.loginWithUsername.requireUsername === false) {
|
||||||
usernameField.required = false
|
usernameField.required = false
|
||||||
}
|
}
|
||||||
|
if (authConfig.loginWithUsername.allowEmailLogin === false) {
|
||||||
|
emailField.unique = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
13
packages/payload/src/auth/getLoginOptions.ts
Normal file
13
packages/payload/src/auth/getLoginOptions.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import type { Auth } from './types.js'
|
||||||
|
|
||||||
|
export const getLoginOptions = (
|
||||||
|
loginWithUsername: Auth['loginWithUsername'],
|
||||||
|
): {
|
||||||
|
canLoginWithEmail: boolean
|
||||||
|
canLoginWithUsername: boolean
|
||||||
|
} => {
|
||||||
|
return {
|
||||||
|
canLoginWithEmail: !loginWithUsername || loginWithUsername.allowEmailLogin,
|
||||||
|
canLoginWithUsername: Boolean(loginWithUsername),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ import { APIError } from '../../errors/index.js'
|
|||||||
import { commitTransaction } from '../../utilities/commitTransaction.js'
|
import { commitTransaction } from '../../utilities/commitTransaction.js'
|
||||||
import { initTransaction } from '../../utilities/initTransaction.js'
|
import { initTransaction } from '../../utilities/initTransaction.js'
|
||||||
import { killTransaction } from '../../utilities/killTransaction.js'
|
import { killTransaction } from '../../utilities/killTransaction.js'
|
||||||
|
import { getLoginOptions } from '../getLoginOptions.js'
|
||||||
|
|
||||||
export type Arguments<TSlug extends CollectionSlug> = {
|
export type Arguments<TSlug extends CollectionSlug> = {
|
||||||
collection: Collection
|
collection: Collection
|
||||||
@@ -33,8 +34,7 @@ export const forgotPasswordOperation = async <TSlug extends CollectionSlug>(
|
|||||||
const loginWithUsername = incomingArgs.collection.config.auth.loginWithUsername
|
const loginWithUsername = incomingArgs.collection.config.auth.loginWithUsername
|
||||||
const { data } = incomingArgs
|
const { data } = incomingArgs
|
||||||
|
|
||||||
const canLoginWithUsername = Boolean(loginWithUsername)
|
const { canLoginWithEmail, canLoginWithUsername } = getLoginOptions(loginWithUsername)
|
||||||
const canLoginWithEmail = !loginWithUsername || loginWithUsername.allowEmailLogin
|
|
||||||
|
|
||||||
const sanitizedEmail =
|
const sanitizedEmail =
|
||||||
(canLoginWithEmail && (incomingArgs.data.email || '').toLowerCase().trim()) || null
|
(canLoginWithEmail && (incomingArgs.data.email || '').toLowerCase().trim()) || null
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { afterRead } from '../../fields/hooks/afterRead/index.js'
|
|||||||
import { killTransaction } from '../../utilities/killTransaction.js'
|
import { killTransaction } from '../../utilities/killTransaction.js'
|
||||||
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields.js'
|
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields.js'
|
||||||
import { getFieldsToSign } from '../getFieldsToSign.js'
|
import { getFieldsToSign } from '../getFieldsToSign.js'
|
||||||
|
import { getLoginOptions } from '../getLoginOptions.js'
|
||||||
import isLocked from '../isLocked.js'
|
import isLocked from '../isLocked.js'
|
||||||
import { jwtSign } from '../jwt.js'
|
import { jwtSign } from '../jwt.js'
|
||||||
import { authenticateLocalStrategy } from '../strategies/local/authenticate.js'
|
import { authenticateLocalStrategy } from '../strategies/local/authenticate.js'
|
||||||
@@ -87,8 +88,7 @@ export const loginOperation = async <TSlug extends CollectionSlug>(
|
|||||||
? data.username.toLowerCase().trim()
|
? data.username.toLowerCase().trim()
|
||||||
: null
|
: null
|
||||||
|
|
||||||
const canLoginWithUsername = Boolean(loginWithUsername)
|
const { canLoginWithEmail, canLoginWithUsername } = getLoginOptions(loginWithUsername)
|
||||||
const canLoginWithEmail = !loginWithUsername || loginWithUsername.allowEmailLogin
|
|
||||||
|
|
||||||
// cannot login with email, did not provide username
|
// cannot login with email, did not provide username
|
||||||
if (!canLoginWithEmail && !sanitizedUsername) {
|
if (!canLoginWithEmail && !sanitizedUsername) {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { commitTransaction } from '../../utilities/commitTransaction.js'
|
|||||||
import { initTransaction } from '../../utilities/initTransaction.js'
|
import { initTransaction } from '../../utilities/initTransaction.js'
|
||||||
import { killTransaction } from '../../utilities/killTransaction.js'
|
import { killTransaction } from '../../utilities/killTransaction.js'
|
||||||
import executeAccess from '../executeAccess.js'
|
import executeAccess from '../executeAccess.js'
|
||||||
|
import { getLoginOptions } from '../getLoginOptions.js'
|
||||||
import { resetLoginAttempts } from '../strategies/local/resetLoginAttempts.js'
|
import { resetLoginAttempts } from '../strategies/local/resetLoginAttempts.js'
|
||||||
|
|
||||||
export type Arguments<TSlug extends CollectionSlug> = {
|
export type Arguments<TSlug extends CollectionSlug> = {
|
||||||
@@ -32,8 +33,8 @@ export const unlockOperation = async <TSlug extends CollectionSlug>(
|
|||||||
} = args
|
} = args
|
||||||
|
|
||||||
const loginWithUsername = collectionConfig.auth.loginWithUsername
|
const loginWithUsername = collectionConfig.auth.loginWithUsername
|
||||||
const canLoginWithUsername = Boolean(loginWithUsername)
|
|
||||||
const canLoginWithEmail = !loginWithUsername || loginWithUsername.allowEmailLogin
|
const { canLoginWithEmail, canLoginWithUsername } = getLoginOptions(loginWithUsername)
|
||||||
|
|
||||||
const sanitizedEmail = canLoginWithEmail && (args.data?.email || '').toLowerCase().trim()
|
const sanitizedEmail = canLoginWithEmail && (args.data?.email || '').toLowerCase().trim()
|
||||||
const sanitizedUsername =
|
const sanitizedUsername =
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { JsonObject, Payload } from '../../../index.js'
|
|||||||
import type { PayloadRequest, SelectType, Where } from '../../../types/index.js'
|
import type { PayloadRequest, SelectType, Where } from '../../../types/index.js'
|
||||||
|
|
||||||
import { ValidationError } from '../../../errors/index.js'
|
import { ValidationError } from '../../../errors/index.js'
|
||||||
|
import { getLoginOptions } from '../../getLoginOptions.js'
|
||||||
import { generatePasswordSaltHash } from './generatePasswordSaltHash.js'
|
import { generatePasswordSaltHash } from './generatePasswordSaltHash.js'
|
||||||
|
|
||||||
type Args = {
|
type Args = {
|
||||||
@@ -24,9 +25,11 @@ export const registerLocalStrategy = async ({
|
|||||||
}: Args): Promise<Record<string, unknown>> => {
|
}: Args): Promise<Record<string, unknown>> => {
|
||||||
const loginWithUsername = collection?.auth?.loginWithUsername
|
const loginWithUsername = collection?.auth?.loginWithUsername
|
||||||
|
|
||||||
|
const { canLoginWithEmail, canLoginWithUsername } = getLoginOptions(loginWithUsername)
|
||||||
|
|
||||||
let whereConstraint: Where
|
let whereConstraint: Where
|
||||||
|
|
||||||
if (!loginWithUsername) {
|
if (!canLoginWithUsername) {
|
||||||
whereConstraint = {
|
whereConstraint = {
|
||||||
email: {
|
email: {
|
||||||
equals: doc.email,
|
equals: doc.email,
|
||||||
@@ -37,7 +40,7 @@ export const registerLocalStrategy = async ({
|
|||||||
or: [],
|
or: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
if (doc.email) {
|
if (canLoginWithEmail && doc.email) {
|
||||||
whereConstraint.or?.push({
|
whereConstraint.or?.push({
|
||||||
email: {
|
email: {
|
||||||
equals: doc.email,
|
equals: doc.email,
|
||||||
@@ -67,7 +70,7 @@ export const registerLocalStrategy = async ({
|
|||||||
throw new ValidationError({
|
throw new ValidationError({
|
||||||
collection: collection.slug,
|
collection: collection.slug,
|
||||||
errors: [
|
errors: [
|
||||||
loginWithUsername
|
canLoginWithUsername
|
||||||
? {
|
? {
|
||||||
message: req.t('error:usernameAlreadyRegistered'),
|
message: req.t('error:usernameAlreadyRegistered'),
|
||||||
path: 'username',
|
path: 'username',
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export {
|
|||||||
getCookieExpiration,
|
getCookieExpiration,
|
||||||
parseCookies,
|
parseCookies,
|
||||||
} from '../auth/cookies.js'
|
} from '../auth/cookies.js'
|
||||||
|
export { getLoginOptions } from '../auth/getLoginOptions.js'
|
||||||
export { getFromImportMap } from '../bin/generateImportMap/getFromImportMap.js'
|
export { getFromImportMap } from '../bin/generateImportMap/getFromImportMap.js'
|
||||||
export { parsePayloadComponent } from '../bin/generateImportMap/parsePayloadComponent.js'
|
export { parsePayloadComponent } from '../bin/generateImportMap/parsePayloadComponent.js'
|
||||||
export { defaults as collectionDefaults } from '../collections/config/defaults.js'
|
export { defaults as collectionDefaults } from '../collections/config/defaults.js'
|
||||||
|
|||||||
Reference in New Issue
Block a user