fix(server): unicode email validation (#30871)
This commit is contained in:
@@ -19664,7 +19664,7 @@
|
||||
"description": "User email",
|
||||
"example": "testuser@email.com",
|
||||
"format": "email",
|
||||
"pattern": "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$",
|
||||
"pattern": "^[\\p{L}\\p{M}\\p{N}.!#$%&'*+/=?^_`{|}~-]+@[\\p{L}\\p{N}](?:[\\p{L}\\p{M}\\p{N}-]{0,61}[\\p{L}\\p{M}\\p{N}])?(?:\\.[\\p{L}\\p{N}](?:[\\p{L}\\p{M}\\p{N}-]{0,61}[\\p{L}\\p{M}\\p{N}])?)*$",
|
||||
"type": "string"
|
||||
},
|
||||
"password": {
|
||||
@@ -19708,7 +19708,7 @@
|
||||
"userEmail": {
|
||||
"description": "User email",
|
||||
"format": "email",
|
||||
"pattern": "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$",
|
||||
"pattern": "^[\\p{L}\\p{M}\\p{N}.!#$%&'*+/=?^_`{|}~-]+@[\\p{L}\\p{N}](?:[\\p{L}\\p{M}\\p{N}-]{0,61}[\\p{L}\\p{M}\\p{N}])?(?:\\.[\\p{L}\\p{N}](?:[\\p{L}\\p{M}\\p{N}-]{0,61}[\\p{L}\\p{M}\\p{N}])?)*$",
|
||||
"type": "string"
|
||||
},
|
||||
"userId": {
|
||||
@@ -20861,7 +20861,7 @@
|
||||
"email": {
|
||||
"description": "User email",
|
||||
"format": "email",
|
||||
"pattern": "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$",
|
||||
"pattern": "^[\\p{L}\\p{M}\\p{N}.!#$%&'*+/=?^_`{|}~-]+@[\\p{L}\\p{N}](?:[\\p{L}\\p{M}\\p{N}-]{0,61}[\\p{L}\\p{M}\\p{N}])?(?:\\.[\\p{L}\\p{N}](?:[\\p{L}\\p{M}\\p{N}-]{0,61}[\\p{L}\\p{M}\\p{N}])?)*$",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
@@ -23429,7 +23429,7 @@
|
||||
"description": "User email",
|
||||
"example": "testuser@email.com",
|
||||
"format": "email",
|
||||
"pattern": "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$",
|
||||
"pattern": "^[\\p{L}\\p{M}\\p{N}.!#$%&'*+/=?^_`{|}~-]+@[\\p{L}\\p{N}](?:[\\p{L}\\p{M}\\p{N}-]{0,61}[\\p{L}\\p{M}\\p{N}])?(?:\\.[\\p{L}\\p{N}](?:[\\p{L}\\p{M}\\p{N}-]{0,61}[\\p{L}\\p{M}\\p{N}])?)*$",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
@@ -27488,7 +27488,7 @@
|
||||
"email": {
|
||||
"description": "User email",
|
||||
"format": "email",
|
||||
"pattern": "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$",
|
||||
"pattern": "^[\\p{L}\\p{M}\\p{N}.!#$%&'*+/=?^_`{|}~-]+@[\\p{L}\\p{N}](?:[\\p{L}\\p{M}\\p{N}-]{0,61}[\\p{L}\\p{M}\\p{N}])?(?:\\.[\\p{L}\\p{N}](?:[\\p{L}\\p{M}\\p{N}-]{0,61}[\\p{L}\\p{M}\\p{N}])?)*$",
|
||||
"type": "string"
|
||||
},
|
||||
"isAdmin": {
|
||||
@@ -27570,7 +27570,7 @@
|
||||
"email": {
|
||||
"description": "User email",
|
||||
"format": "email",
|
||||
"pattern": "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$",
|
||||
"pattern": "^[\\p{L}\\p{M}\\p{N}.!#$%&'*+/=?^_`{|}~-]+@[\\p{L}\\p{N}](?:[\\p{L}\\p{M}\\p{N}-]{0,61}[\\p{L}\\p{M}\\p{N}])?(?:\\.[\\p{L}\\p{N}](?:[\\p{L}\\p{M}\\p{N}-]{0,61}[\\p{L}\\p{M}\\p{N}])?)*$",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
@@ -27676,7 +27676,7 @@
|
||||
"email": {
|
||||
"description": "User email",
|
||||
"format": "email",
|
||||
"pattern": "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$",
|
||||
"pattern": "^[\\p{L}\\p{M}\\p{N}.!#$%&'*+/=?^_`{|}~-]+@[\\p{L}\\p{N}](?:[\\p{L}\\p{M}\\p{N}-]{0,61}[\\p{L}\\p{M}\\p{N}])?(?:\\.[\\p{L}\\p{N}](?:[\\p{L}\\p{M}\\p{N}-]{0,61}[\\p{L}\\p{M}\\p{N}])?)*$",
|
||||
"type": "string"
|
||||
},
|
||||
"isAdmin": {
|
||||
@@ -27875,7 +27875,7 @@
|
||||
"email": {
|
||||
"description": "User email",
|
||||
"format": "email",
|
||||
"pattern": "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$",
|
||||
"pattern": "^[\\p{L}\\p{M}\\p{N}.!#$%&'*+/=?^_`{|}~-]+@[\\p{L}\\p{N}](?:[\\p{L}\\p{M}\\p{N}-]{0,61}[\\p{L}\\p{M}\\p{N}])?(?:\\.[\\p{L}\\p{N}](?:[\\p{L}\\p{M}\\p{N}-]{0,61}[\\p{L}\\p{M}\\p{N}])?)*$",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
@@ -27930,7 +27930,7 @@
|
||||
"email": {
|
||||
"description": "User email",
|
||||
"format": "email",
|
||||
"pattern": "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$",
|
||||
"pattern": "^[\\p{L}\\p{M}\\p{N}.!#$%&'*+/=?^_`{|}~-]+@[\\p{L}\\p{N}](?:[\\p{L}\\p{M}\\p{N}-]{0,61}[\\p{L}\\p{M}\\p{N}])?(?:\\.[\\p{L}\\p{N}](?:[\\p{L}\\p{M}\\p{N}-]{0,61}[\\p{L}\\p{M}\\p{N}])?)*$",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IsNotSiblingOf } from 'src/validation';
|
||||
import { IsNotSiblingOf, toEmail } from 'src/validation';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import z from 'zod';
|
||||
|
||||
@@ -42,4 +42,32 @@ describe('Validation', () => {
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toEmail', () => {
|
||||
it.each([
|
||||
'test@immich.cloud',
|
||||
'test@immich',
|
||||
'first.last+tag@immich.cloud',
|
||||
// unicode local parts and internationalized domain names
|
||||
'tëst@immich.cloud',
|
||||
'leoñ@immich.cloud',
|
||||
'test@яндекс.рф',
|
||||
'test@xn--d1acpjx3f.xn--p1ai',
|
||||
'用户@例子.广告',
|
||||
'test@ท่องเที่ยว.ไทย',
|
||||
])('should accept %s', (email) => {
|
||||
expect(toEmail.safeParse(email).success).toBe(true);
|
||||
});
|
||||
|
||||
it.each(['immich', 'test@@immich.cloud', 'test user@immich.cloud', 'test@immich..cloud', 'test@-immich.cloud'])(
|
||||
'should reject %s',
|
||||
(email) => {
|
||||
expect(toEmail.safeParse(email).success).toBe(false);
|
||||
},
|
||||
);
|
||||
|
||||
it('should convert the email to lower case', () => {
|
||||
expect(toEmail.parse('tÉst@Immich.Cloud')).toBe('tést@immich.cloud');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -119,14 +119,22 @@ const FilenameParamSchema = z.object({
|
||||
|
||||
export class FilenameParamDto extends createZodDto(FilenameParamSchema) {}
|
||||
|
||||
/**
|
||||
* The HTML5 email regex, but with unicode support, so that international email addresses
|
||||
* (unicode local parts and internationalized domain names) are accepted.
|
||||
* @see {@link z.regexes.html5Email}
|
||||
*/
|
||||
const unicodeEmail =
|
||||
/^[\p{L}\p{M}\p{N}.!#$%&'*+/=?^_`{|}~-]+@[\p{L}\p{N}](?:[\p{L}\p{M}\p{N}-]{0,61}[\p{L}\p{M}\p{N}])?(?:\.[\p{L}\p{N}](?:[\p{L}\p{M}\p{N}-]{0,61}[\p{L}\p{M}\p{N}])?)*$/u;
|
||||
|
||||
/**
|
||||
* Unified email validation
|
||||
* Converts email strings to lowercase and validates against HTML5 email regex
|
||||
* Converts email strings to lowercase and validates against a unicode aware email regex
|
||||
* @docs https://zod.dev/api?id=email
|
||||
*/
|
||||
export const toEmail = z
|
||||
.email({
|
||||
pattern: z.regexes.html5Email,
|
||||
pattern: unicodeEmail,
|
||||
error: (iss) => `Invalid input: expected email, received ${typeof iss.input}`,
|
||||
})
|
||||
.transform((val) => val.toLowerCase());
|
||||
|
||||
Reference in New Issue
Block a user