diff --git a/packages/graphql/src/schema/initCollections.ts b/packages/graphql/src/schema/initCollections.ts index 3c8322ece..8c7d51019 100644 --- a/packages/graphql/src/schema/initCollections.ts +++ b/packages/graphql/src/schema/initCollections.ts @@ -14,7 +14,7 @@ import { GraphQLString, } from 'graphql' import { buildVersionCollectionFields, flattenTopLevelFields, formatNames, toWords } from 'payload' -import { fieldAffectsData } from 'payload/shared' +import { fieldAffectsData, getLoginOptions } from 'payload/shared' import type { ObjectTypeConfig } from './buildObjectType.js' @@ -442,10 +442,9 @@ export function initCollections({ config, graphqlResult }: InitCollectionsGraphQ if (!collectionConfig.auth.disableLocalStrategy) { const authArgs = {} - const canLoginWithEmail = - !collectionConfig.auth.loginWithUsername || - collectionConfig.auth.loginWithUsername?.allowEmailLogin - const canLoginWithUsername = collectionConfig.auth.loginWithUsername + const { canLoginWithEmail, canLoginWithUsername } = getLoginOptions( + collectionConfig.auth.loginWithUsername, + ) if (canLoginWithEmail) { authArgs['email'] = { type: new GraphQLNonNull(GraphQLString) } diff --git a/packages/next/src/views/Login/LoginForm/index.tsx b/packages/next/src/views/Login/LoginForm/index.tsx index a1193e0dd..a361b3704 100644 --- a/packages/next/src/views/Login/LoginForm/index.tsx +++ b/packages/next/src/views/Login/LoginForm/index.tsx @@ -11,6 +11,7 @@ import type { FormState } from 'payload' import { Form, FormSubmit, PasswordField, useAuth, useConfig, useTranslation } from '@payloadcms/ui' import { formatAdminURL } from '@payloadcms/ui/shared' +import { getLoginOptions } from 'payload/shared' 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 { auth: authOptions } = collectionConfig const loginWithUsername = authOptions.loginWithUsername - const canLoginWithEmail = - !authOptions.loginWithUsername || authOptions.loginWithUsername.allowEmailLogin - const canLoginWithUsername = authOptions.loginWithUsername + const { canLoginWithEmail, canLoginWithUsername } = getLoginOptions(loginWithUsername) const [loginType] = React.useState(() => { if (canLoginWithEmail && canLoginWithUsername) { diff --git a/packages/payload/src/auth/getAuthFields.ts b/packages/payload/src/auth/getAuthFields.ts index 702d12193..122c43984 100644 --- a/packages/payload/src/auth/getAuthFields.ts +++ b/packages/payload/src/auth/getAuthFields.ts @@ -28,6 +28,9 @@ export const getBaseAuthFields = (authConfig: IncomingAuthType): Field[] => { if (authConfig.loginWithUsername.requireUsername === false) { usernameField.required = false } + if (authConfig.loginWithUsername.allowEmailLogin === false) { + emailField.unique = false + } } } diff --git a/packages/payload/src/auth/getLoginOptions.ts b/packages/payload/src/auth/getLoginOptions.ts new file mode 100644 index 000000000..4a4fdb6be --- /dev/null +++ b/packages/payload/src/auth/getLoginOptions.ts @@ -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), + } +} diff --git a/packages/payload/src/auth/operations/forgotPassword.ts b/packages/payload/src/auth/operations/forgotPassword.ts index 8b6d1b479..082bef0bd 100644 --- a/packages/payload/src/auth/operations/forgotPassword.ts +++ b/packages/payload/src/auth/operations/forgotPassword.ts @@ -14,6 +14,7 @@ import { APIError } from '../../errors/index.js' import { commitTransaction } from '../../utilities/commitTransaction.js' import { initTransaction } from '../../utilities/initTransaction.js' import { killTransaction } from '../../utilities/killTransaction.js' +import { getLoginOptions } from '../getLoginOptions.js' export type Arguments = { collection: Collection @@ -33,8 +34,7 @@ export const forgotPasswordOperation = async ( const loginWithUsername = incomingArgs.collection.config.auth.loginWithUsername const { data } = incomingArgs - const canLoginWithUsername = Boolean(loginWithUsername) - const canLoginWithEmail = !loginWithUsername || loginWithUsername.allowEmailLogin + const { canLoginWithEmail, canLoginWithUsername } = getLoginOptions(loginWithUsername) const sanitizedEmail = (canLoginWithEmail && (incomingArgs.data.email || '').toLowerCase().trim()) || null diff --git a/packages/payload/src/auth/operations/login.ts b/packages/payload/src/auth/operations/login.ts index f9c54f913..bbc2c28a9 100644 --- a/packages/payload/src/auth/operations/login.ts +++ b/packages/payload/src/auth/operations/login.ts @@ -13,6 +13,7 @@ import { afterRead } from '../../fields/hooks/afterRead/index.js' import { killTransaction } from '../../utilities/killTransaction.js' import sanitizeInternalFields from '../../utilities/sanitizeInternalFields.js' import { getFieldsToSign } from '../getFieldsToSign.js' +import { getLoginOptions } from '../getLoginOptions.js' import isLocked from '../isLocked.js' import { jwtSign } from '../jwt.js' import { authenticateLocalStrategy } from '../strategies/local/authenticate.js' @@ -87,8 +88,7 @@ export const loginOperation = async ( ? data.username.toLowerCase().trim() : null - const canLoginWithUsername = Boolean(loginWithUsername) - const canLoginWithEmail = !loginWithUsername || loginWithUsername.allowEmailLogin + const { canLoginWithEmail, canLoginWithUsername } = getLoginOptions(loginWithUsername) // cannot login with email, did not provide username if (!canLoginWithEmail && !sanitizedUsername) { diff --git a/packages/payload/src/auth/operations/unlock.ts b/packages/payload/src/auth/operations/unlock.ts index f6d331835..a6b6e44ee 100644 --- a/packages/payload/src/auth/operations/unlock.ts +++ b/packages/payload/src/auth/operations/unlock.ts @@ -12,6 +12,7 @@ import { commitTransaction } from '../../utilities/commitTransaction.js' import { initTransaction } from '../../utilities/initTransaction.js' import { killTransaction } from '../../utilities/killTransaction.js' import executeAccess from '../executeAccess.js' +import { getLoginOptions } from '../getLoginOptions.js' import { resetLoginAttempts } from '../strategies/local/resetLoginAttempts.js' export type Arguments = { @@ -32,8 +33,8 @@ export const unlockOperation = async ( } = args 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 sanitizedUsername = diff --git a/packages/payload/src/auth/strategies/local/register.ts b/packages/payload/src/auth/strategies/local/register.ts index fede389d3..c4bc266b7 100644 --- a/packages/payload/src/auth/strategies/local/register.ts +++ b/packages/payload/src/auth/strategies/local/register.ts @@ -3,6 +3,7 @@ import type { JsonObject, Payload } from '../../../index.js' import type { PayloadRequest, SelectType, Where } from '../../../types/index.js' import { ValidationError } from '../../../errors/index.js' +import { getLoginOptions } from '../../getLoginOptions.js' import { generatePasswordSaltHash } from './generatePasswordSaltHash.js' type Args = { @@ -24,9 +25,11 @@ export const registerLocalStrategy = async ({ }: Args): Promise> => { const loginWithUsername = collection?.auth?.loginWithUsername + const { canLoginWithEmail, canLoginWithUsername } = getLoginOptions(loginWithUsername) + let whereConstraint: Where - if (!loginWithUsername) { + if (!canLoginWithUsername) { whereConstraint = { email: { equals: doc.email, @@ -37,7 +40,7 @@ export const registerLocalStrategy = async ({ or: [], } - if (doc.email) { + if (canLoginWithEmail && doc.email) { whereConstraint.or?.push({ email: { equals: doc.email, @@ -67,7 +70,7 @@ export const registerLocalStrategy = async ({ throw new ValidationError({ collection: collection.slug, errors: [ - loginWithUsername + canLoginWithUsername ? { message: req.t('error:usernameAlreadyRegistered'), path: 'username', diff --git a/packages/payload/src/exports/shared.ts b/packages/payload/src/exports/shared.ts index 76ff98576..29f7d6c12 100644 --- a/packages/payload/src/exports/shared.ts +++ b/packages/payload/src/exports/shared.ts @@ -5,6 +5,7 @@ export { getCookieExpiration, parseCookies, } from '../auth/cookies.js' +export { getLoginOptions } from '../auth/getLoginOptions.js' export { getFromImportMap } from '../bin/generateImportMap/getFromImportMap.js' export { parsePayloadComponent } from '../bin/generateImportMap/parsePayloadComponent.js' export { defaults as collectionDefaults } from '../collections/config/defaults.js'