Skip to content

Commit 67a9d66

Browse files
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`.
1 parent f19053e commit 67a9d66

File tree

9 files changed

+36
-17
lines changed

9 files changed

+36
-17
lines changed

packages/graphql/src/schema/initCollections.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
GraphQLString,
1515
} from 'graphql'
1616
import { buildVersionCollectionFields, flattenTopLevelFields, formatNames, toWords } from 'payload'
17-
import { fieldAffectsData } from 'payload/shared'
17+
import { fieldAffectsData, getLoginOptions } from 'payload/shared'
1818

1919
import type { ObjectTypeConfig } from './buildObjectType.js'
2020

@@ -442,10 +442,9 @@ export function initCollections({ config, graphqlResult }: InitCollectionsGraphQ
442442
if (!collectionConfig.auth.disableLocalStrategy) {
443443
const authArgs = {}
444444

445-
const canLoginWithEmail =
446-
!collectionConfig.auth.loginWithUsername ||
447-
collectionConfig.auth.loginWithUsername?.allowEmailLogin
448-
const canLoginWithUsername = collectionConfig.auth.loginWithUsername
445+
const { canLoginWithEmail, canLoginWithUsername } = getLoginOptions(
446+
collectionConfig.auth.loginWithUsername,
447+
)
449448

450449
if (canLoginWithEmail) {
451450
authArgs['email'] = { type: new GraphQLNonNull(GraphQLString) }

packages/next/src/views/Login/LoginForm/index.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import type { FormState } from 'payload'
1111

1212
import { Form, FormSubmit, PasswordField, useAuth, useConfig, useTranslation } from '@payloadcms/ui'
1313
import { formatAdminURL } from '@payloadcms/ui/shared'
14+
import { getLoginOptions } from 'payload/shared'
1415

1516
import type { LoginFieldProps } from '../LoginField/index.js'
1617

@@ -36,9 +37,7 @@ export const LoginForm: React.FC<{
3637
const collectionConfig = config.collections?.find((collection) => collection?.slug === userSlug)
3738
const { auth: authOptions } = collectionConfig
3839
const loginWithUsername = authOptions.loginWithUsername
39-
const canLoginWithEmail =
40-
!authOptions.loginWithUsername || authOptions.loginWithUsername.allowEmailLogin
41-
const canLoginWithUsername = authOptions.loginWithUsername
40+
const { canLoginWithEmail, canLoginWithUsername } = getLoginOptions(loginWithUsername)
4241

4342
const [loginType] = React.useState<LoginFieldProps['type']>(() => {
4443
if (canLoginWithEmail && canLoginWithUsername) {

packages/payload/src/auth/getAuthFields.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ export const getBaseAuthFields = (authConfig: IncomingAuthType): Field[] => {
2828
if (authConfig.loginWithUsername.requireUsername === false) {
2929
usernameField.required = false
3030
}
31+
if (authConfig.loginWithUsername.allowEmailLogin === false) {
32+
emailField.unique = false
33+
}
3134
}
3235
}
3336

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import type { Auth } from './types.js'
2+
3+
export const getLoginOptions = (
4+
loginWithUsername: Auth['loginWithUsername'],
5+
): {
6+
canLoginWithEmail: boolean
7+
canLoginWithUsername: boolean
8+
} => {
9+
return {
10+
canLoginWithEmail: !loginWithUsername || loginWithUsername.allowEmailLogin,
11+
canLoginWithUsername: Boolean(loginWithUsername),
12+
}
13+
}

packages/payload/src/auth/operations/forgotPassword.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { APIError } from '../../errors/index.js'
1414
import { commitTransaction } from '../../utilities/commitTransaction.js'
1515
import { initTransaction } from '../../utilities/initTransaction.js'
1616
import { killTransaction } from '../../utilities/killTransaction.js'
17+
import { getLoginOptions } from '../getLoginOptions.js'
1718

1819
export type Arguments<TSlug extends CollectionSlug> = {
1920
collection: Collection
@@ -33,8 +34,7 @@ export const forgotPasswordOperation = async <TSlug extends CollectionSlug>(
3334
const loginWithUsername = incomingArgs.collection.config.auth.loginWithUsername
3435
const { data } = incomingArgs
3536

36-
const canLoginWithUsername = Boolean(loginWithUsername)
37-
const canLoginWithEmail = !loginWithUsername || loginWithUsername.allowEmailLogin
37+
const { canLoginWithEmail, canLoginWithUsername } = getLoginOptions(loginWithUsername)
3838

3939
const sanitizedEmail =
4040
(canLoginWithEmail && (incomingArgs.data.email || '').toLowerCase().trim()) || null

packages/payload/src/auth/operations/login.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { afterRead } from '../../fields/hooks/afterRead/index.js'
1313
import { killTransaction } from '../../utilities/killTransaction.js'
1414
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields.js'
1515
import { getFieldsToSign } from '../getFieldsToSign.js'
16+
import { getLoginOptions } from '../getLoginOptions.js'
1617
import isLocked from '../isLocked.js'
1718
import { jwtSign } from '../jwt.js'
1819
import { authenticateLocalStrategy } from '../strategies/local/authenticate.js'
@@ -87,8 +88,7 @@ export const loginOperation = async <TSlug extends CollectionSlug>(
8788
? data.username.toLowerCase().trim()
8889
: null
8990

90-
const canLoginWithUsername = Boolean(loginWithUsername)
91-
const canLoginWithEmail = !loginWithUsername || loginWithUsername.allowEmailLogin
91+
const { canLoginWithEmail, canLoginWithUsername } = getLoginOptions(loginWithUsername)
9292

9393
// cannot login with email, did not provide username
9494
if (!canLoginWithEmail && !sanitizedUsername) {

packages/payload/src/auth/operations/unlock.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { commitTransaction } from '../../utilities/commitTransaction.js'
1212
import { initTransaction } from '../../utilities/initTransaction.js'
1313
import { killTransaction } from '../../utilities/killTransaction.js'
1414
import executeAccess from '../executeAccess.js'
15+
import { getLoginOptions } from '../getLoginOptions.js'
1516
import { resetLoginAttempts } from '../strategies/local/resetLoginAttempts.js'
1617

1718
export type Arguments<TSlug extends CollectionSlug> = {
@@ -32,8 +33,8 @@ export const unlockOperation = async <TSlug extends CollectionSlug>(
3233
} = args
3334

3435
const loginWithUsername = collectionConfig.auth.loginWithUsername
35-
const canLoginWithUsername = Boolean(loginWithUsername)
36-
const canLoginWithEmail = !loginWithUsername || loginWithUsername.allowEmailLogin
36+
37+
const { canLoginWithEmail, canLoginWithUsername } = getLoginOptions(loginWithUsername)
3738

3839
const sanitizedEmail = canLoginWithEmail && (args.data?.email || '').toLowerCase().trim()
3940
const sanitizedUsername =

packages/payload/src/auth/strategies/local/register.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { JsonObject, Payload } from '../../../index.js'
33
import type { PayloadRequest, SelectType, Where } from '../../../types/index.js'
44

55
import { ValidationError } from '../../../errors/index.js'
6+
import { getLoginOptions } from '../../getLoginOptions.js'
67
import { generatePasswordSaltHash } from './generatePasswordSaltHash.js'
78

89
type Args = {
@@ -24,9 +25,11 @@ export const registerLocalStrategy = async ({
2425
}: Args): Promise<Record<string, unknown>> => {
2526
const loginWithUsername = collection?.auth?.loginWithUsername
2627

28+
const { canLoginWithEmail, canLoginWithUsername } = getLoginOptions(loginWithUsername)
29+
2730
let whereConstraint: Where
2831

29-
if (!loginWithUsername) {
32+
if (!canLoginWithUsername) {
3033
whereConstraint = {
3134
email: {
3235
equals: doc.email,
@@ -37,7 +40,7 @@ export const registerLocalStrategy = async ({
3740
or: [],
3841
}
3942

40-
if (doc.email) {
43+
if (canLoginWithEmail && doc.email) {
4144
whereConstraint.or?.push({
4245
email: {
4346
equals: doc.email,
@@ -67,7 +70,7 @@ export const registerLocalStrategy = async ({
6770
throw new ValidationError({
6871
collection: collection.slug,
6972
errors: [
70-
loginWithUsername
73+
canLoginWithUsername
7174
? {
7275
message: req.t('error:usernameAlreadyRegistered'),
7376
path: 'username',

packages/payload/src/exports/shared.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export {
55
getCookieExpiration,
66
parseCookies,
77
} from '../auth/cookies.js'
8+
export { getLoginOptions } from '../auth/getLoginOptions.js'
89
export { getFromImportMap } from '../bin/generateImportMap/getFromImportMap.js'
910
export { parsePayloadComponent } from '../bin/generateImportMap/parsePayloadComponent.js'
1011
export { defaults as collectionDefaults } from '../collections/config/defaults.js'

0 commit comments

Comments
 (0)