Skip to content

Commit afe4432

Browse files
authored
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.
1 parent ef527fe commit afe4432

File tree

2 files changed

+72
-6
lines changed

2 files changed

+72
-6
lines changed

packages/payload/src/fields/validations.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -192,12 +192,15 @@ export const email: EmailFieldValidation = (
192192

193193
/**
194194
* Disallows emails with double quotes (e.g., "user"@example.com, user@"example.com", "user@example.com")
195-
* Rejects spaces anywhere in the email (e.g., user @example.com)
196-
* Prevents consecutive dots (e.g., user..name@example.com)
197-
* Ensures a valid domain (e.g., rejects user@example, user@example..com)
198-
* Allows standard formats like user@example.com, user.name+alias@example.co.uk
195+
* Rejects spaces anywhere in the email (e.g., user @example.com, user@ example.com, user name@example.com)
196+
* Prevents consecutive dots in the local or domain part (e.g., user..name@example.com, user@example..com)
197+
* Disallows domains that start or end with a hyphen (e.g., user@-example.com, user@example-.com)
198+
* Allows standard email formats (e.g., user@example.com, user.name+alias@example.co.uk, user-name@example.org)
199+
* Allows domains with consecutive hyphens as long as they are not leading/trailing (e.g., user@ex--ample.com)
200+
* Supports multiple subdomains (e.g., user@sub.domain.example.com)
199201
*/
200-
const emailRegex = /^(?!.*\.\.)[\w.%+-]+@[a-z0-9.-]+\.[a-z]{2,}$/i
202+
const emailRegex =
203+
/^(?!.*\.\.)[\w.%+-]+@[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)*\.[a-z]{2,}$/i
201204

202205
if ((value && !emailRegex.test(value)) || (!value && required)) {
203206
return t('validation:emailAddress')

test/auth/int.spec.ts

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
1-
import type { FieldAffectingData, Payload, User } from 'payload'
1+
import type {
2+
BasePayload,
3+
EmailFieldValidation,
4+
FieldAffectingData,
5+
Payload,
6+
SanitizedConfig,
7+
User,
8+
} from 'payload'
29

310
import { jwtDecode } from 'jwt-decode'
411
import path from 'path'
12+
import { email as emailValidation } from 'payload/shared'
513
import { fileURLToPath } from 'url'
614
import { v4 as uuid } from 'uuid'
715

@@ -969,4 +977,59 @@ describe('Auth', () => {
969977
).rejects.toThrow('Token is either invalid or has expired.')
970978
})
971979
})
980+
981+
describe('Email - format validation', () => {
982+
const mockT = jest.fn((key) => key) // Mocks translation function
983+
984+
const mockContext: Parameters<EmailFieldValidation>[1] = {
985+
// @ts-expect-error: Mocking context for email validation
986+
req: {
987+
payload: {
988+
collections: {} as Record<string, never>,
989+
config: {} as SanitizedConfig,
990+
} as unknown as BasePayload,
991+
t: mockT,
992+
},
993+
required: true,
994+
siblingData: {},
995+
blockData: {},
996+
data: {},
997+
path: ['email'],
998+
preferences: { fields: {} },
999+
}
1000+
it('should allow standard formatted emails', () => {
1001+
expect(emailValidation('user@example.com', mockContext)).toBe(true)
1002+
expect(emailValidation('user.name+alias@example.co.uk', mockContext)).toBe(true)
1003+
expect(emailValidation('user-name@example.org', mockContext)).toBe(true)
1004+
expect(emailValidation('user@ex--ample.com', mockContext)).toBe(true)
1005+
})
1006+
1007+
it('should not allow emails with double quotes', () => {
1008+
expect(emailValidation('"user"@example.com', mockContext)).toBe('validation:emailAddress')
1009+
expect(emailValidation('user@"example.com"', mockContext)).toBe('validation:emailAddress')
1010+
expect(emailValidation('"user@example.com"', mockContext)).toBe('validation:emailAddress')
1011+
})
1012+
1013+
it('should not allow emails with spaces', () => {
1014+
expect(emailValidation('user @example.com', mockContext)).toBe('validation:emailAddress')
1015+
expect(emailValidation('user@ example.com', mockContext)).toBe('validation:emailAddress')
1016+
expect(emailValidation('user name@example.com', mockContext)).toBe('validation:emailAddress')
1017+
})
1018+
1019+
it('should not allow emails with consecutive dots', () => {
1020+
expect(emailValidation('user..name@example.com', mockContext)).toBe('validation:emailAddress')
1021+
expect(emailValidation('user@example..com', mockContext)).toBe('validation:emailAddress')
1022+
})
1023+
1024+
it('should not allow emails with invalid domains', () => {
1025+
expect(emailValidation('user@example', mockContext)).toBe('validation:emailAddress')
1026+
expect(emailValidation('user@example..com', mockContext)).toBe('validation:emailAddress')
1027+
expect(emailValidation('user@example.c', mockContext)).toBe('validation:emailAddress')
1028+
})
1029+
1030+
it('should not allow domains starting or ending with a hyphen', () => {
1031+
expect(emailValidation('user@-example.com', mockContext)).toBe('validation:emailAddress')
1032+
expect(emailValidation('user@example-.com', mockContext)).toBe('validation:emailAddress')
1033+
})
1034+
})
9721035
})

0 commit comments

Comments
 (0)