Explorar o código

allow emails without a tld (#2762)

It's perfectly valid to have an email address without a TLD, for instance:

- test@localhost
- test@svc-in-same-k8s-namespace
- test@internal-corp

Fixes #2667
Thomas %!s(int64=2) %!d(string=hai) anos
pai
achega
408fa45c51

+ 9 - 0
server/src/domain/auth/dto/login-credential.dto.spec.ts

@@ -3,6 +3,15 @@ import { validateSync } from 'class-validator';
 import { LoginCredentialDto } from './login-credential.dto';
 
 describe('LoginCredentialDto', () => {
+  it('should allow emails without a tld', () => {
+    const someEmail = 'test@test';
+
+    const dto = plainToInstance(LoginCredentialDto, { email: someEmail, password: 'password' });
+    const errors = validateSync(dto);
+    expect(errors).toHaveLength(0);
+    expect(dto.email).toEqual(someEmail);
+  });
+
   it('should fail without an email', () => {
     const dto = plainToInstance(LoginCredentialDto, { password: 'password' });
     const errors = validateSync(dto);

+ 1 - 1
server/src/domain/auth/dto/login-credential.dto.ts

@@ -3,7 +3,7 @@ import { Transform } from 'class-transformer';
 import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
 
 export class LoginCredentialDto {
-  @IsEmail()
+  @IsEmail({ require_tld: false })
   @ApiProperty({ example: 'testuser@email.com' })
   @Transform(({ value }) => value.toLowerCase())
   email!: string;

+ 14 - 0
server/src/domain/auth/dto/sign-up.dto.spec.ts

@@ -30,6 +30,20 @@ describe('SignUpDto', () => {
     expect(errors[0].property).toEqual('email');
   });
 
+  it('should allow emails without a tld', () => {
+    const someEmail = 'test@test';
+
+    const dto = plainToInstance(SignUpDto, {
+      email: someEmail,
+      password: 'password',
+      firstName: 'first name',
+      lastName: 'last name',
+    });
+    const errors = validateSync(dto);
+    expect(errors).toHaveLength(0);
+    expect(dto.email).toEqual(someEmail);
+  });
+
   it('should make the email all lowercase', () => {
     const dto = plainToInstance(SignUpDto, {
       email: 'TeSt@ImMiCh.com',

+ 1 - 1
server/src/domain/auth/dto/sign-up.dto.ts

@@ -3,7 +3,7 @@ import { Transform } from 'class-transformer';
 import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
 
 export class SignUpDto {
-  @IsEmail()
+  @IsEmail({ require_tld: false })
   @ApiProperty({ example: 'testuser@email.com' })
   @Transform(({ value }) => value.toLowerCase())
   email!: string;

+ 48 - 1
server/src/domain/user/dto/create-user.dto.spec.ts

@@ -1,6 +1,6 @@
 import { plainToInstance } from 'class-transformer';
 import { validate } from 'class-validator';
-import { CreateUserDto } from './create-user.dto';
+import { CreateAdminDto, CreateUserDto, CreateUserOAuthDto } from './create-user.dto';
 
 describe('create user DTO', () => {
   it('validates the email', async () => {
@@ -24,4 +24,51 @@ describe('create user DTO', () => {
     errors = await validate(dto);
     expect(errors).toHaveLength(0);
   });
+
+  it('should allow emails without a tld', async () => {
+    const someEmail = 'test@test';
+
+    const dto = plainToInstance(CreateUserDto, {
+      email: someEmail,
+      password: 'some password',
+      firstName: 'some first name',
+      lastName: 'some last name',
+    });
+    const errors = await validate(dto);
+    expect(errors).toHaveLength(0);
+    expect(dto.email).toEqual(someEmail);
+  });
+});
+
+describe('create admin DTO', () => {
+  it('should allow emails without a tld', async () => {
+    const someEmail = 'test@test';
+
+    const dto = plainToInstance(CreateAdminDto, {
+      isAdmin: true,
+      email: someEmail,
+      password: 'some password',
+      firstName: 'some first name',
+      lastName: 'some last name',
+    });
+    const errors = await validate(dto);
+    expect(errors).toHaveLength(0);
+    expect(dto.email).toEqual(someEmail);
+  });
+});
+
+describe('create user oauth DTO', () => {
+  it('should allow emails without a tld', async () => {
+    const someEmail = 'test@test';
+
+    const dto = plainToInstance(CreateUserOAuthDto, {
+      email: someEmail,
+      oauthId: 'some oauth id',
+      firstName: 'some first name',
+      lastName: 'some last name',
+    });
+    const errors = await validate(dto);
+    expect(errors).toHaveLength(0);
+    expect(dto.email).toEqual(someEmail);
+  });
 });

+ 3 - 3
server/src/domain/user/dto/create-user.dto.ts

@@ -3,7 +3,7 @@ import { IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator';
 import { toEmail, toSanitized } from '@app/immich/utils/transform.util';
 
 export class CreateUserDto {
-  @IsEmail()
+  @IsEmail({ require_tld: false })
   @Transform(toEmail)
   email!: string;
 
@@ -29,7 +29,7 @@ export class CreateAdminDto {
   @IsNotEmpty()
   isAdmin!: true;
 
-  @IsEmail()
+  @IsEmail({ require_tld: false })
   @Transform(({ value }) => value?.toLowerCase())
   email!: string;
 
@@ -44,7 +44,7 @@ export class CreateAdminDto {
 }
 
 export class CreateUserOAuthDto {
-  @IsEmail()
+  @IsEmail({ require_tld: false })
   @Transform(({ value }) => value?.toLowerCase())
   email!: string;
 

+ 17 - 0
server/src/domain/user/dto/update-user.dto.spec.ts

@@ -0,0 +1,17 @@
+import { plainToInstance } from 'class-transformer';
+import { validate } from 'class-validator';
+import { UpdateUserDto } from './update-user.dto';
+
+describe('update user DTO', () => {
+  it('should allow emails without a tld', async () => {
+    const someEmail = 'test@test';
+
+    const dto = plainToInstance(UpdateUserDto, {
+      email: someEmail,
+      id: '3fe388e4-2078-44d7-b36c-39d9dee3a657',
+    });
+    const errors = await validate(dto);
+    expect(errors).toHaveLength(0);
+    expect(dto.email).toEqual(someEmail);
+  });
+});

+ 1 - 1
server/src/domain/user/dto/update-user.dto.ts

@@ -5,7 +5,7 @@ import { toEmail, toSanitized } from '@app/immich/utils/transform.util';
 
 export class UpdateUserDto {
   @IsOptional()
-  @IsEmail()
+  @IsEmail({ require_tld: false })
   @Transform(toEmail)
   email?: string;