fix: email format validation with hyphens (#11761)

This PR updates the email validation regex to better handle use cases
with hyphens.

Changes:

- Disallows domains starting or ending with a hyphen
(`user@-example.com`, `user@example-.com`).
- Allows domains with consecutive hyphens inside (`user@ex--ample.com`).
- Allows multiple subdomains (`user@sub.domain.example.com`).
- Adds `int test` coverage for multiple domain use case scenarios.
This commit is contained in:
Patrik
2025-03-19 09:24:45 -04:00
committed by GitHub
parent ef527fe2d4
commit afe443267d
2 changed files with 72 additions and 6 deletions

View File

@@ -192,12 +192,15 @@ export const email: EmailFieldValidation = (
/**
* Disallows emails with double quotes (e.g., "user"@example.com, user@"example.com", "user@example.com")
* Rejects spaces anywhere in the email (e.g., user @example.com)
* Prevents consecutive dots (e.g., user..name@example.com)
* Ensures a valid domain (e.g., rejects user@example, user@example..com)
* Allows standard formats like user@example.com, user.name+alias@example.co.uk
* Rejects spaces anywhere in the email (e.g., user @example.com, user@ example.com, user name@example.com)
* Prevents consecutive dots in the local or domain part (e.g., user..name@example.com, user@example..com)
* Disallows domains that start or end with a hyphen (e.g., user@-example.com, user@example-.com)
* Allows standard email formats (e.g., user@example.com, user.name+alias@example.co.uk, user-name@example.org)
* Allows domains with consecutive hyphens as long as they are not leading/trailing (e.g., user@ex--ample.com)
* Supports multiple subdomains (e.g., user@sub.domain.example.com)
*/
const emailRegex = /^(?!.*\.\.)[\w.%+-]+@[a-z0-9.-]+\.[a-z]{2,}$/i
const emailRegex =
/^(?!.*\.\.)[\w.%+-]+@[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)*\.[a-z]{2,}$/i
if ((value && !emailRegex.test(value)) || (!value && required)) {
return t('validation:emailAddress')

View File

@@ -1,7 +1,15 @@
import type { FieldAffectingData, Payload, User } from 'payload'
import type {
BasePayload,
EmailFieldValidation,
FieldAffectingData,
Payload,
SanitizedConfig,
User,
} from 'payload'
import { jwtDecode } from 'jwt-decode'
import path from 'path'
import { email as emailValidation } from 'payload/shared'
import { fileURLToPath } from 'url'
import { v4 as uuid } from 'uuid'
@@ -969,4 +977,59 @@ describe('Auth', () => {
).rejects.toThrow('Token is either invalid or has expired.')
})
})
describe('Email - format validation', () => {
const mockT = jest.fn((key) => key) // Mocks translation function
const mockContext: Parameters<EmailFieldValidation>[1] = {
// @ts-expect-error: Mocking context for email validation
req: {
payload: {
collections: {} as Record<string, never>,
config: {} as SanitizedConfig,
} as unknown as BasePayload,
t: mockT,
},
required: true,
siblingData: {},
blockData: {},
data: {},
path: ['email'],
preferences: { fields: {} },
}
it('should allow standard formatted emails', () => {
expect(emailValidation('user@example.com', mockContext)).toBe(true)
expect(emailValidation('user.name+alias@example.co.uk', mockContext)).toBe(true)
expect(emailValidation('user-name@example.org', mockContext)).toBe(true)
expect(emailValidation('user@ex--ample.com', mockContext)).toBe(true)
})
it('should not allow emails with double quotes', () => {
expect(emailValidation('"user"@example.com', mockContext)).toBe('validation:emailAddress')
expect(emailValidation('user@"example.com"', mockContext)).toBe('validation:emailAddress')
expect(emailValidation('"user@example.com"', mockContext)).toBe('validation:emailAddress')
})
it('should not allow emails with spaces', () => {
expect(emailValidation('user @example.com', mockContext)).toBe('validation:emailAddress')
expect(emailValidation('user@ example.com', mockContext)).toBe('validation:emailAddress')
expect(emailValidation('user name@example.com', mockContext)).toBe('validation:emailAddress')
})
it('should not allow emails with consecutive dots', () => {
expect(emailValidation('user..name@example.com', mockContext)).toBe('validation:emailAddress')
expect(emailValidation('user@example..com', mockContext)).toBe('validation:emailAddress')
})
it('should not allow emails with invalid domains', () => {
expect(emailValidation('user@example', mockContext)).toBe('validation:emailAddress')
expect(emailValidation('user@example..com', mockContext)).toBe('validation:emailAddress')
expect(emailValidation('user@example.c', mockContext)).toBe('validation:emailAddress')
})
it('should not allow domains starting or ending with a hyphen', () => {
expect(emailValidation('user@-example.com', mockContext)).toBe('validation:emailAddress')
expect(emailValidation('user@example-.com', mockContext)).toBe('validation:emailAddress')
})
})
})