From c584791b65c88bfc327cfbc55407502362897f14 Mon Sep 17 00:00:00 2001 From: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com> Date: Mon, 3 Apr 2023 06:24:18 +0200 Subject: [PATCH] feat(server): improve validation in controllers (#2149) * feat(server): improve validation in controllers * set ValidationPipe config with decorator --- .../apps/immich/src/controllers/album.controller.ts | 5 +++-- .../immich/src/controllers/api-key.controller.ts | 5 +++-- .../apps/immich/src/controllers/auth.controller.ts | 5 +++-- .../immich/src/controllers/device-info.controller.ts | 5 +++-- server/apps/immich/src/controllers/job.controller.ts | 5 +++-- .../apps/immich/src/controllers/oauth.controller.ts | 5 +++-- .../apps/immich/src/controllers/search.controller.ts | 5 +++-- .../immich/src/controllers/server-info.controller.ts | 5 +++-- .../apps/immich/src/controllers/share.controller.ts | 5 +++-- .../src/controllers/system-config.controller.ts | 5 +++-- .../apps/immich/src/controllers/user.controller.ts | 5 ++--- .../src/decorators/use-validation.decorator.ts | 12 ++++++++++++ .../src/system-config/dto/system-config.dto.ts | 11 ++++++++++- 13 files changed, 54 insertions(+), 24 deletions(-) create mode 100644 server/apps/immich/src/decorators/use-validation.decorator.ts diff --git a/server/apps/immich/src/controllers/album.controller.ts b/server/apps/immich/src/controllers/album.controller.ts index 0680fd318c..ec8ecd28e1 100644 --- a/server/apps/immich/src/controllers/album.controller.ts +++ b/server/apps/immich/src/controllers/album.controller.ts @@ -1,14 +1,15 @@ import { AlbumService, AuthUserDto } from '@app/domain'; import { GetAlbumsDto } from '@app/domain/album/dto/get-albums.dto'; -import { Controller, Get, Query, UsePipes, ValidationPipe } from '@nestjs/common'; +import { Controller, Get, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { GetAuthUser } from '../decorators/auth-user.decorator'; import { Authenticated } from '../decorators/authenticated.decorator'; +import { UseValidation } from '../decorators/use-validation.decorator'; @ApiTags('Album') @Controller('album') @Authenticated() -@UsePipes(new ValidationPipe({ transform: true })) +@UseValidation() export class AlbumController { constructor(private service: AlbumService) {} diff --git a/server/apps/immich/src/controllers/api-key.controller.ts b/server/apps/immich/src/controllers/api-key.controller.ts index 9804a72029..d666bd4272 100644 --- a/server/apps/immich/src/controllers/api-key.controller.ts +++ b/server/apps/immich/src/controllers/api-key.controller.ts @@ -6,15 +6,16 @@ import { APIKeyUpdateDto, AuthUserDto, } from '@app/domain'; -import { Body, Controller, Delete, Get, Param, Post, Put, UsePipes, ValidationPipe } from '@nestjs/common'; +import { Body, Controller, Delete, Get, Param, Post, Put } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { GetAuthUser } from '../decorators/auth-user.decorator'; import { Authenticated } from '../decorators/authenticated.decorator'; +import { UseValidation } from '../decorators/use-validation.decorator'; @ApiTags('API Key') @Controller('api-key') @Authenticated() -@UsePipes(new ValidationPipe({ transform: true })) +@UseValidation() export class APIKeyController { constructor(private service: APIKeyService) {} diff --git a/server/apps/immich/src/controllers/auth.controller.ts b/server/apps/immich/src/controllers/auth.controller.ts index 55659b2def..7107a8bebf 100644 --- a/server/apps/immich/src/controllers/auth.controller.ts +++ b/server/apps/immich/src/controllers/auth.controller.ts @@ -13,15 +13,16 @@ import { UserResponseDto, ValidateAccessTokenResponseDto, } from '@app/domain'; -import { Body, Controller, Ip, Post, Req, Res, UsePipes, ValidationPipe } from '@nestjs/common'; +import { Body, Controller, Ip, Post, Req, Res } from '@nestjs/common'; import { ApiBadRequestResponse, ApiTags } from '@nestjs/swagger'; import { Request, Response } from 'express'; import { GetAuthUser } from '../decorators/auth-user.decorator'; import { Authenticated } from '../decorators/authenticated.decorator'; +import { UseValidation } from '../decorators/use-validation.decorator'; @ApiTags('Authentication') @Controller('auth') -@UsePipes(new ValidationPipe({ transform: true })) +@UseValidation() export class AuthController { constructor(private readonly service: AuthService) {} diff --git a/server/apps/immich/src/controllers/device-info.controller.ts b/server/apps/immich/src/controllers/device-info.controller.ts index 755300aab3..6d2c8262b2 100644 --- a/server/apps/immich/src/controllers/device-info.controller.ts +++ b/server/apps/immich/src/controllers/device-info.controller.ts @@ -4,15 +4,16 @@ import { DeviceInfoService, UpsertDeviceInfoDto as UpsertDto, } from '@app/domain'; -import { Body, Controller, Put, UsePipes, ValidationPipe } from '@nestjs/common'; +import { Body, Controller, Put } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { GetAuthUser } from '../decorators/auth-user.decorator'; import { Authenticated } from '../decorators/authenticated.decorator'; +import { UseValidation } from '../decorators/use-validation.decorator'; @ApiTags('Device Info') @Controller('device-info') @Authenticated() -@UsePipes(new ValidationPipe({ transform: true })) +@UseValidation() export class DeviceInfoController { constructor(private readonly service: DeviceInfoService) {} diff --git a/server/apps/immich/src/controllers/job.controller.ts b/server/apps/immich/src/controllers/job.controller.ts index f86b60e1b2..b0d441dd02 100644 --- a/server/apps/immich/src/controllers/job.controller.ts +++ b/server/apps/immich/src/controllers/job.controller.ts @@ -1,12 +1,13 @@ import { AllJobStatusResponseDto, JobCommandDto, JobStatusDto, JobIdDto, JobService } from '@app/domain'; -import { Body, Controller, Get, Param, Put, UsePipes, ValidationPipe } from '@nestjs/common'; +import { Body, Controller, Get, Param, Put } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { Authenticated } from '../decorators/authenticated.decorator'; +import { UseValidation } from '../decorators/use-validation.decorator'; @ApiTags('Job') @Controller('jobs') @Authenticated({ admin: true }) -@UsePipes(new ValidationPipe({ transform: true })) +@UseValidation() export class JobController { constructor(private service: JobService) {} diff --git a/server/apps/immich/src/controllers/oauth.controller.ts b/server/apps/immich/src/controllers/oauth.controller.ts index 16e0a3b53d..0423234379 100644 --- a/server/apps/immich/src/controllers/oauth.controller.ts +++ b/server/apps/immich/src/controllers/oauth.controller.ts @@ -7,15 +7,16 @@ import { OAuthService, UserResponseDto, } from '@app/domain'; -import { Body, Controller, Get, HttpStatus, Post, Redirect, Req, Res, UsePipes, ValidationPipe } from '@nestjs/common'; +import { Body, Controller, Get, HttpStatus, Post, Redirect, Req, Res } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { Request, Response } from 'express'; import { GetAuthUser } from '../decorators/auth-user.decorator'; import { Authenticated } from '../decorators/authenticated.decorator'; +import { UseValidation } from '../decorators/use-validation.decorator'; @ApiTags('OAuth') @Controller('oauth') -@UsePipes(new ValidationPipe({ transform: true })) +@UseValidation() export class OAuthController { constructor(private service: OAuthService) {} diff --git a/server/apps/immich/src/controllers/search.controller.ts b/server/apps/immich/src/controllers/search.controller.ts index c9a5cb046d..0fb3b79fd1 100644 --- a/server/apps/immich/src/controllers/search.controller.ts +++ b/server/apps/immich/src/controllers/search.controller.ts @@ -6,15 +6,16 @@ import { SearchResponseDto, SearchService, } from '@app/domain'; -import { Controller, Get, Query, UsePipes, ValidationPipe } from '@nestjs/common'; +import { Controller, Get, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { GetAuthUser } from '../decorators/auth-user.decorator'; import { Authenticated } from '../decorators/authenticated.decorator'; +import { UseValidation } from '../decorators/use-validation.decorator'; @ApiTags('Search') @Controller('search') @Authenticated() -@UsePipes(new ValidationPipe({ transform: true })) +@UseValidation() export class SearchController { constructor(private service: SearchService) {} diff --git a/server/apps/immich/src/controllers/server-info.controller.ts b/server/apps/immich/src/controllers/server-info.controller.ts index 97c6e30f92..86a05525ca 100644 --- a/server/apps/immich/src/controllers/server-info.controller.ts +++ b/server/apps/immich/src/controllers/server-info.controller.ts @@ -5,13 +5,14 @@ import { ServerStatsResponseDto, ServerVersionReponseDto, } from '@app/domain'; -import { Controller, Get, UsePipes, ValidationPipe } from '@nestjs/common'; +import { Controller, Get } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { Authenticated } from '../decorators/authenticated.decorator'; +import { UseValidation } from '../decorators/use-validation.decorator'; @ApiTags('Server Info') @Controller('server-info') -@UsePipes(new ValidationPipe({ transform: true })) +@UseValidation() export class ServerInfoController { constructor(private service: ServerInfoService) {} diff --git a/server/apps/immich/src/controllers/share.controller.ts b/server/apps/immich/src/controllers/share.controller.ts index d6d460a779..72b0926d2b 100644 --- a/server/apps/immich/src/controllers/share.controller.ts +++ b/server/apps/immich/src/controllers/share.controller.ts @@ -1,12 +1,13 @@ import { AuthUserDto, EditSharedLinkDto, SharedLinkResponseDto, ShareService } from '@app/domain'; -import { Body, Controller, Delete, Get, Param, Patch, UsePipes, ValidationPipe } from '@nestjs/common'; +import { Body, Controller, Delete, Get, Param, Patch } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { GetAuthUser } from '../decorators/auth-user.decorator'; import { Authenticated } from '../decorators/authenticated.decorator'; +import { UseValidation } from '../decorators/use-validation.decorator'; @ApiTags('share') @Controller('share') -@UsePipes(new ValidationPipe({ transform: true })) +@UseValidation() export class ShareController { constructor(private readonly service: ShareService) {} diff --git a/server/apps/immich/src/controllers/system-config.controller.ts b/server/apps/immich/src/controllers/system-config.controller.ts index 4a5debe40d..fb7e38b362 100644 --- a/server/apps/immich/src/controllers/system-config.controller.ts +++ b/server/apps/immich/src/controllers/system-config.controller.ts @@ -1,12 +1,13 @@ import { SystemConfigDto, SystemConfigService, SystemConfigTemplateStorageOptionDto } from '@app/domain'; -import { Body, Controller, Get, Put, UsePipes, ValidationPipe } from '@nestjs/common'; +import { Body, Controller, Get, Put } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { Authenticated } from '../decorators/authenticated.decorator'; +import { UseValidation } from '../decorators/use-validation.decorator'; @ApiTags('System Config') @Controller('system-config') @Authenticated({ admin: true }) -@UsePipes(new ValidationPipe({ transform: true })) +@UseValidation() export class SystemConfigController { constructor(private readonly service: SystemConfigService) {} diff --git a/server/apps/immich/src/controllers/user.controller.ts b/server/apps/immich/src/controllers/user.controller.ts index 9c180d2cb3..9e7bd311fe 100644 --- a/server/apps/immich/src/controllers/user.controller.ts +++ b/server/apps/immich/src/controllers/user.controller.ts @@ -5,7 +5,6 @@ import { Delete, Body, Param, - ValidationPipe, Put, Query, UseInterceptors, @@ -13,7 +12,6 @@ import { Response, StreamableFile, Header, - UsePipes, } from '@nestjs/common'; import { UserService } from '@app/domain'; import { Authenticated } from '../decorators/authenticated.decorator'; @@ -29,10 +27,11 @@ import { UserCountResponseDto } from '@app/domain'; import { CreateProfileImageDto } from '@app/domain'; import { CreateProfileImageResponseDto } from '@app/domain'; import { UserCountDto } from '@app/domain'; +import { UseValidation } from '../decorators/use-validation.decorator'; @ApiTags('User') @Controller('user') -@UsePipes(new ValidationPipe({ transform: true, whitelist: true })) +@UseValidation() export class UserController { constructor(private service: UserService) {} diff --git a/server/apps/immich/src/decorators/use-validation.decorator.ts b/server/apps/immich/src/decorators/use-validation.decorator.ts new file mode 100644 index 0000000000..a9b40ea40f --- /dev/null +++ b/server/apps/immich/src/decorators/use-validation.decorator.ts @@ -0,0 +1,12 @@ +import { applyDecorators, UsePipes, ValidationPipe } from '@nestjs/common'; + +export function UseValidation() { + return applyDecorators( + UsePipes( + new ValidationPipe({ + transform: true, + whitelist: true, + }), + ), + ); +} diff --git a/server/libs/domain/src/system-config/dto/system-config.dto.ts b/server/libs/domain/src/system-config/dto/system-config.dto.ts index e593ed96da..92dc51bef1 100644 --- a/server/libs/domain/src/system-config/dto/system-config.dto.ts +++ b/server/libs/domain/src/system-config/dto/system-config.dto.ts @@ -1,21 +1,30 @@ import { SystemConfig } from '@app/infra/entities'; -import { ValidateNested } from 'class-validator'; +import { Type } from 'class-transformer'; +import { IsObject, ValidateNested } from 'class-validator'; import { SystemConfigFFmpegDto } from './system-config-ffmpeg.dto'; import { SystemConfigOAuthDto } from './system-config-oauth.dto'; import { SystemConfigPasswordLoginDto } from './system-config-password-login.dto'; import { SystemConfigStorageTemplateDto } from './system-config-storage-template.dto'; export class SystemConfigDto { + @Type(() => SystemConfigFFmpegDto) @ValidateNested() + @IsObject() ffmpeg!: SystemConfigFFmpegDto; + @Type(() => SystemConfigOAuthDto) @ValidateNested() + @IsObject() oauth!: SystemConfigOAuthDto; + @Type(() => SystemConfigPasswordLoginDto) @ValidateNested() + @IsObject() passwordLogin!: SystemConfigPasswordLoginDto; + @Type(() => SystemConfigStorageTemplateDto) @ValidateNested() + @IsObject() storageTemplate!: SystemConfigStorageTemplateDto; }