diff --git a/server/e2e/album.e2e-spec.ts b/server/e2e/album.e2e-spec.ts index c1a877e4c1..bf0006d91c 100644 --- a/server/e2e/album.e2e-spec.ts +++ b/server/e2e/album.e2e-spec.ts @@ -1,13 +1,13 @@ import { AlbumResponseDto, AuthService, + AuthUserDto, CreateAlbumDto, SharedLinkCreateDto, SharedLinkResponseDto, UserService, } from '@app/domain'; import { AppModule } from '@app/immich/app.module'; -import { AuthUserDto } from '@app/immich/decorators/auth-user.decorator'; import { SharedLinkType } from '@app/infra/entities'; import { INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; diff --git a/server/src/domain/album/dto/album-add-users.dto.ts b/server/src/domain/album/dto/album-add-users.dto.ts index 39ecb7b50c..f238b9a054 100644 --- a/server/src/domain/album/dto/album-add-users.dto.ts +++ b/server/src/domain/album/dto/album-add-users.dto.ts @@ -1,5 +1,5 @@ -import { ValidateUUID } from '@app/immich/decorators/validate-uuid.decorator'; import { ArrayNotEmpty } from 'class-validator'; +import { ValidateUUID } from '../../domain.util'; export class AddUsersDto { @ValidateUUID({ each: true }) diff --git a/server/src/domain/album/dto/album-create.dto.ts b/server/src/domain/album/dto/album-create.dto.ts index 2edef34573..3d99683319 100644 --- a/server/src/domain/album/dto/album-create.dto.ts +++ b/server/src/domain/album/dto/album-create.dto.ts @@ -1,6 +1,6 @@ -import { ValidateUUID } from '@app/immich/decorators/validate-uuid.decorator'; import { ApiProperty } from '@nestjs/swagger'; import { IsNotEmpty, IsString } from 'class-validator'; +import { ValidateUUID } from '../../domain.util'; export class CreateAlbumDto { @IsNotEmpty() diff --git a/server/src/domain/album/dto/album-update.dto.ts b/server/src/domain/album/dto/album-update.dto.ts index 9388eac67a..9bbe16e3ba 100644 --- a/server/src/domain/album/dto/album-update.dto.ts +++ b/server/src/domain/album/dto/album-update.dto.ts @@ -1,6 +1,6 @@ -import { ValidateUUID } from '@app/immich/decorators/validate-uuid.decorator'; import { ApiProperty } from '@nestjs/swagger'; import { IsOptional } from 'class-validator'; +import { ValidateUUID } from '../../domain.util'; export class UpdateAlbumDto { @IsOptional() diff --git a/server/src/domain/album/dto/get-albums.dto.ts b/server/src/domain/album/dto/get-albums.dto.ts index 6a76478819..ebe2080bb3 100644 --- a/server/src/domain/album/dto/get-albums.dto.ts +++ b/server/src/domain/album/dto/get-albums.dto.ts @@ -1,8 +1,7 @@ -import { ValidateUUID } from '@app/immich/decorators/validate-uuid.decorator'; -import { toBoolean } from '@app/immich/utils/transform.util'; import { ApiProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; import { IsBoolean, IsOptional } from 'class-validator'; +import { toBoolean, ValidateUUID } from '../../domain.util'; export class GetAlbumsDto { @IsOptional() diff --git a/server/src/domain/asset/dto/asset-ids.dto.ts b/server/src/domain/asset/dto/asset-ids.dto.ts index 5438e7a63d..6d2c58528d 100644 --- a/server/src/domain/asset/dto/asset-ids.dto.ts +++ b/server/src/domain/asset/dto/asset-ids.dto.ts @@ -1,4 +1,4 @@ -import { ValidateUUID } from '@app/immich/decorators/validate-uuid.decorator'; +import { ValidateUUID } from '../../domain.util'; export class AssetIdsDto { @ValidateUUID({ each: true }) diff --git a/server/src/domain/asset/dto/download.dto.ts b/server/src/domain/asset/dto/download.dto.ts index cb6b8f7dde..c2cf85685a 100644 --- a/server/src/domain/asset/dto/download.dto.ts +++ b/server/src/domain/asset/dto/download.dto.ts @@ -1,6 +1,6 @@ -import { ValidateUUID } from '@app/immich/decorators/validate-uuid.decorator'; import { ApiProperty } from '@nestjs/swagger'; import { IsInt, IsOptional, IsPositive } from 'class-validator'; +import { ValidateUUID } from '../../domain.util'; export class DownloadDto { @ValidateUUID({ each: true, optional: true }) diff --git a/server/src/domain/asset/dto/map-marker.dto.ts b/server/src/domain/asset/dto/map-marker.dto.ts index a8e3b79243..682cb24367 100644 --- a/server/src/domain/asset/dto/map-marker.dto.ts +++ b/server/src/domain/asset/dto/map-marker.dto.ts @@ -1,7 +1,7 @@ -import { toBoolean } from '@app/immich/utils/transform.util'; import { ApiProperty } from '@nestjs/swagger'; import { Transform, Type } from 'class-transformer'; import { IsBoolean, IsDate, IsOptional } from 'class-validator'; +import { toBoolean } from '../../domain.util'; export class MapMarkerDto { @ApiProperty() diff --git a/server/src/domain/domain.util.ts b/server/src/domain/domain.util.ts index 96fac353e9..c5a6c45b83 100644 --- a/server/src/domain/domain.util.ts +++ b/server/src/domain/domain.util.ts @@ -1,4 +1,39 @@ +import { applyDecorators } from '@nestjs/common'; +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator'; import { basename, extname } from 'node:path'; +import sanitize from 'sanitize-filename'; + +export type Options = { + optional?: boolean; + each?: boolean; +}; + +export function ValidateUUID({ optional, each }: Options = { optional: false, each: false }) { + return applyDecorators( + IsUUID('4', { each }), + ApiProperty({ format: 'uuid' }), + optional ? IsOptional() : IsNotEmpty(), + each ? IsArray() : IsString(), + ); +} + +interface IValue { + value?: string; +} + +export const toBoolean = ({ value }: IValue) => { + if (value == 'true') { + return true; + } else if (value == 'false') { + return false; + } + return value; +}; + +export const toEmail = ({ value }: IValue) => value?.toLowerCase(); + +export const toSanitized = ({ value }: IValue) => sanitize((value || '').replace(/\./g, '')); export function getFileNameWithoutExtension(path: string): string { return basename(path, extname(path)); diff --git a/server/src/domain/search/dto/search.dto.ts b/server/src/domain/search/dto/search.dto.ts index 5a11c68dea..27eeb52725 100644 --- a/server/src/domain/search/dto/search.dto.ts +++ b/server/src/domain/search/dto/search.dto.ts @@ -1,7 +1,7 @@ -import { toBoolean } from '@app/immich/utils/transform.util'; import { AssetType } from '@app/infra/entities'; import { Transform } from 'class-transformer'; import { IsArray, IsBoolean, IsEnum, IsNotEmpty, IsOptional, IsString } from 'class-validator'; +import { toBoolean } from '../../domain.util'; export class SearchDto { @IsString() diff --git a/server/src/domain/shared-link/shared-link.dto.ts b/server/src/domain/shared-link/shared-link.dto.ts index 5aeef2967c..982c1fbe0f 100644 --- a/server/src/domain/shared-link/shared-link.dto.ts +++ b/server/src/domain/shared-link/shared-link.dto.ts @@ -2,7 +2,7 @@ import { SharedLinkType } from '@app/infra/entities'; import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; import { IsBoolean, IsDate, IsEnum, IsOptional, IsString } from 'class-validator'; -import { ValidateUUID } from '../../immich/decorators/validate-uuid.decorator'; +import { ValidateUUID } from '../domain.util'; export class SharedLinkCreateDto { @IsEnum(SharedLinkType) diff --git a/server/src/domain/user/dto/create-user.dto.ts b/server/src/domain/user/dto/create-user.dto.ts index 5951be8312..afb5aec237 100644 --- a/server/src/domain/user/dto/create-user.dto.ts +++ b/server/src/domain/user/dto/create-user.dto.ts @@ -1,6 +1,6 @@ -import { toEmail, toSanitized } from '@app/immich/utils/transform.util'; import { Transform } from 'class-transformer'; import { IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator'; +import { toEmail, toSanitized } from '../../domain.util'; export class CreateUserDto { @IsEmail({ require_tld: false }) diff --git a/server/src/domain/user/dto/update-user.dto.ts b/server/src/domain/user/dto/update-user.dto.ts index fca200ab28..7eb98f5384 100644 --- a/server/src/domain/user/dto/update-user.dto.ts +++ b/server/src/domain/user/dto/update-user.dto.ts @@ -1,7 +1,7 @@ -import { toEmail, toSanitized } from '@app/immich/utils/transform.util'; import { ApiProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; import { IsBoolean, IsEmail, IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator'; +import { toEmail, toSanitized } from '../../domain.util'; export class UpdateUserDto { @IsOptional() diff --git a/server/src/immich/api-v1/album/album.controller.ts b/server/src/immich/api-v1/album/album.controller.ts index 021cc04ce6..ba6f195a24 100644 --- a/server/src/immich/api-v1/album/album.controller.ts +++ b/server/src/immich/api-v1/album/album.controller.ts @@ -1,10 +1,9 @@ -import { AlbumResponseDto } from '@app/domain'; +import { AlbumResponseDto, AuthUserDto } from '@app/domain'; import { Body, Controller, Delete, Get, Param, Put } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; +import { Authenticated, AuthUser, SharedLinkRoute } from '../../app.guard'; +import { UseValidation } from '../../app.utils'; import { UUIDParamDto } from '../../controllers/dto/uuid-param.dto'; -import { AuthUser, AuthUserDto } from '../../decorators/auth-user.decorator'; -import { Authenticated, SharedLinkRoute } from '../../decorators/authenticated.decorator'; -import { UseValidation } from '../../decorators/use-validation.decorator'; import { AlbumService } from './album.service'; import { AddAssetsDto } from './dto/add-assets.dto'; import { RemoveAssetsDto } from './dto/remove-assets.dto'; diff --git a/server/src/immich/api-v1/album/album.service.spec.ts b/server/src/immich/api-v1/album/album.service.spec.ts index 1215e69905..89275b9687 100644 --- a/server/src/immich/api-v1/album/album.service.spec.ts +++ b/server/src/immich/api-v1/album/album.service.spec.ts @@ -1,8 +1,7 @@ -import { AlbumResponseDto, mapUser } from '@app/domain'; +import { AlbumResponseDto, AuthUserDto, mapUser } from '@app/domain'; import { AlbumEntity, UserEntity } from '@app/infra/entities'; import { ForbiddenException, NotFoundException } from '@nestjs/common'; import { userEntityStub } from '@test'; -import { AuthUserDto } from '../../decorators/auth-user.decorator'; import { IAlbumRepository } from './album-repository'; import { AlbumService } from './album.service'; import { AddAssetsResponseDto } from './response-dto/add-assets-response.dto'; diff --git a/server/src/immich/api-v1/album/album.service.ts b/server/src/immich/api-v1/album/album.service.ts index 5f7fc834e6..cb433bcbd6 100644 --- a/server/src/immich/api-v1/album/album.service.ts +++ b/server/src/immich/api-v1/album/album.service.ts @@ -1,7 +1,6 @@ -import { AlbumResponseDto, mapAlbum } from '@app/domain'; +import { AlbumResponseDto, AuthUserDto, mapAlbum } from '@app/domain'; import { AlbumEntity } from '@app/infra/entities'; import { BadRequestException, ForbiddenException, Inject, Injectable, Logger, NotFoundException } from '@nestjs/common'; -import { AuthUserDto } from '../../decorators/auth-user.decorator'; import { IAlbumRepository } from './album-repository'; import { AddAssetsDto } from './dto/add-assets.dto'; import { RemoveAssetsDto } from './dto/remove-assets.dto'; diff --git a/server/src/immich/api-v1/album/dto/add-assets.dto.ts b/server/src/immich/api-v1/album/dto/add-assets.dto.ts index f5c13d677b..9f66fab3d8 100644 --- a/server/src/immich/api-v1/album/dto/add-assets.dto.ts +++ b/server/src/immich/api-v1/album/dto/add-assets.dto.ts @@ -1,4 +1,4 @@ -import { ValidateUUID } from '@app/immich/decorators/validate-uuid.decorator'; +import { ValidateUUID } from '@app/domain'; export class AddAssetsDto { @ValidateUUID({ each: true }) diff --git a/server/src/immich/api-v1/album/dto/add-users.dto.ts b/server/src/immich/api-v1/album/dto/add-users.dto.ts index 921dac7eff..3ff76a8227 100644 --- a/server/src/immich/api-v1/album/dto/add-users.dto.ts +++ b/server/src/immich/api-v1/album/dto/add-users.dto.ts @@ -1,4 +1,4 @@ -import { ValidateUUID } from '@app/immich/decorators/validate-uuid.decorator'; +import { ValidateUUID } from '@app/domain'; export class AddUsersDto { @ValidateUUID({ each: true }) diff --git a/server/src/immich/api-v1/album/dto/remove-assets.dto.ts b/server/src/immich/api-v1/album/dto/remove-assets.dto.ts index 2782154bbc..a663b0254d 100644 --- a/server/src/immich/api-v1/album/dto/remove-assets.dto.ts +++ b/server/src/immich/api-v1/album/dto/remove-assets.dto.ts @@ -1,4 +1,4 @@ -import { ValidateUUID } from '@app/immich/decorators/validate-uuid.decorator'; +import { ValidateUUID } from '@app/domain'; export class RemoveAssetsDto { @ValidateUUID({ each: true }) diff --git a/server/src/immich/api-v1/asset/asset.controller.ts b/server/src/immich/api-v1/asset/asset.controller.ts index 53e323a0ba..c2d3e6b39d 100644 --- a/server/src/immich/api-v1/asset/asset.controller.ts +++ b/server/src/immich/api-v1/asset/asset.controller.ts @@ -1,4 +1,4 @@ -import { AssetResponseDto } from '@app/domain'; +import { AssetResponseDto, AuthUserDto } from '@app/domain'; import { Body, Controller, @@ -21,10 +21,9 @@ import { import { FileFieldsInterceptor } from '@nestjs/platform-express'; import { ApiBody, ApiConsumes, ApiHeader, ApiOkResponse, ApiTags } from '@nestjs/swagger'; import { Response as Res } from 'express'; +import { Authenticated, AuthUser, SharedLinkRoute } from '../../app.guard'; import { assetUploadOption, ImmichFile } from '../../config/asset-upload.config'; import { UUIDParamDto } from '../../controllers/dto/uuid-param.dto'; -import { AuthUser, AuthUserDto } from '../../decorators/auth-user.decorator'; -import { Authenticated, SharedLinkRoute } from '../../decorators/authenticated.decorator'; import FileNotEmptyValidator from '../validation/file-not-empty-validator'; import { AssetService } from './asset.service'; import { AssetBulkUploadCheckDto } from './dto/asset-check.dto'; diff --git a/server/src/immich/api-v1/asset/dto/asset-search.dto.ts b/server/src/immich/api-v1/asset/dto/asset-search.dto.ts index 8a908e746d..a629c915c8 100644 --- a/server/src/immich/api-v1/asset/dto/asset-search.dto.ts +++ b/server/src/immich/api-v1/asset/dto/asset-search.dto.ts @@ -1,7 +1,7 @@ +import { toBoolean } from '@app/domain'; import { ApiProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; import { IsBoolean, IsNotEmpty, IsNumber, IsOptional, IsUUID } from 'class-validator'; -import { toBoolean } from '../../../utils/transform.util'; export class AssetSearchDto { @IsOptional() diff --git a/server/src/immich/api-v1/asset/dto/create-asset.dto.ts b/server/src/immich/api-v1/asset/dto/create-asset.dto.ts index bf54d4ce30..76a24ee184 100644 --- a/server/src/immich/api-v1/asset/dto/create-asset.dto.ts +++ b/server/src/immich/api-v1/asset/dto/create-asset.dto.ts @@ -1,9 +1,9 @@ +import { toBoolean, toSanitized } from '@app/domain'; import { AssetType } from '@app/infra/entities'; import { ApiProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; import { IsBoolean, IsEnum, IsNotEmpty, IsOptional, IsString } from 'class-validator'; import { ImmichFile } from '../../../config/asset-upload.config'; -import { toBoolean, toSanitized } from '../../../utils/transform.util'; export class CreateAssetBase { @IsNotEmpty() diff --git a/server/src/immich/api-v1/asset/dto/get-asset-by-time-bucket.dto.ts b/server/src/immich/api-v1/asset/dto/get-asset-by-time-bucket.dto.ts index ad846751c6..ddf2d2aa51 100644 --- a/server/src/immich/api-v1/asset/dto/get-asset-by-time-bucket.dto.ts +++ b/server/src/immich/api-v1/asset/dto/get-asset-by-time-bucket.dto.ts @@ -1,7 +1,7 @@ +import { toBoolean } from '@app/domain'; import { ApiProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; import { IsBoolean, IsNotEmpty, IsOptional, IsUUID } from 'class-validator'; -import { toBoolean } from '../../../utils/transform.util'; export class GetAssetByTimeBucketDto { @IsNotEmpty() diff --git a/server/src/immich/api-v1/asset/dto/get-asset-count-by-time-bucket.dto.ts b/server/src/immich/api-v1/asset/dto/get-asset-count-by-time-bucket.dto.ts index d4e6c3c45a..f1f564a3b5 100644 --- a/server/src/immich/api-v1/asset/dto/get-asset-count-by-time-bucket.dto.ts +++ b/server/src/immich/api-v1/asset/dto/get-asset-count-by-time-bucket.dto.ts @@ -1,7 +1,7 @@ +import { toBoolean } from '@app/domain'; import { ApiProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; import { IsBoolean, IsNotEmpty, IsOptional, IsUUID } from 'class-validator'; -import { toBoolean } from '../../../utils/transform.util'; export enum TimeGroupEnum { Day = 'day', diff --git a/server/src/immich/api-v1/asset/dto/serve-file.dto.ts b/server/src/immich/api-v1/asset/dto/serve-file.dto.ts index a4c01b07c1..45ab4f2b4a 100644 --- a/server/src/immich/api-v1/asset/dto/serve-file.dto.ts +++ b/server/src/immich/api-v1/asset/dto/serve-file.dto.ts @@ -1,7 +1,7 @@ +import { toBoolean } from '@app/domain'; import { ApiProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; import { IsBoolean, IsOptional } from 'class-validator'; -import { toBoolean } from '../../../utils/transform.util'; export class ServeFileDto { @IsOptional() diff --git a/server/src/immich/app.guard.ts b/server/src/immich/app.guard.ts new file mode 100644 index 0000000000..f104fe7bda --- /dev/null +++ b/server/src/immich/app.guard.ts @@ -0,0 +1,118 @@ +import { AuthService, AuthUserDto, IMMICH_API_KEY_NAME, LoginDetails } from '@app/domain'; +import { + applyDecorators, + CanActivate, + createParamDecorator, + ExecutionContext, + Injectable, + Logger, + SetMetadata, +} from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { ApiBearerAuth, ApiCookieAuth, ApiQuery, ApiSecurity } from '@nestjs/swagger'; +import { Request } from 'express'; +import { UAParser } from 'ua-parser-js'; + +export enum Metadata { + AUTH_ROUTE = 'auth_route', + ADMIN_ROUTE = 'admin_route', + SHARED_ROUTE = 'shared_route', + PUBLIC_SECURITY = 'public_security', +} + +const adminDecorator = SetMetadata(Metadata.ADMIN_ROUTE, true); + +const sharedLinkDecorators = [ + SetMetadata(Metadata.SHARED_ROUTE, true), + ApiQuery({ name: 'key', type: String, required: false }), +]; + +export interface AuthenticatedOptions { + admin?: boolean; + isShared?: boolean; +} + +export const Authenticated = (options: AuthenticatedOptions = {}) => { + const decorators: MethodDecorator[] = [ + ApiBearerAuth(), + ApiCookieAuth(), + ApiSecurity(IMMICH_API_KEY_NAME), + SetMetadata(Metadata.AUTH_ROUTE, true), + ]; + + if (options.admin) { + decorators.push(adminDecorator); + } + + if (options.isShared) { + decorators.push(...sharedLinkDecorators); + } + + return applyDecorators(...decorators); +}; + +export const PublicRoute = () => + applyDecorators(SetMetadata(Metadata.AUTH_ROUTE, false), ApiSecurity(Metadata.PUBLIC_SECURITY)); +export const SharedLinkRoute = () => applyDecorators(...sharedLinkDecorators); +export const AdminRoute = () => adminDecorator; + +export const AuthUser = createParamDecorator((data, ctx: ExecutionContext): AuthUserDto => { + return ctx.switchToHttp().getRequest<{ user: AuthUserDto }>().user; +}); + +export const GetLoginDetails = createParamDecorator((data, ctx: ExecutionContext): LoginDetails => { + const req = ctx.switchToHttp().getRequest(); + const userAgent = UAParser(req.headers['user-agent']); + + return { + clientIp: req.clientIp, + isSecure: req.secure, + deviceType: userAgent.browser.name || userAgent.device.type || req.headers.devicemodel || '', + deviceOS: userAgent.os.name || req.headers.devicetype || '', + }; +}); + +export interface AuthRequest extends Request { + user?: AuthUserDto; +} + +@Injectable() +export class AppGuard implements CanActivate { + private logger = new Logger(AppGuard.name); + + constructor(private reflector: Reflector, private authService: AuthService) {} + + async canActivate(context: ExecutionContext): Promise { + const targets = [context.getHandler(), context.getClass()]; + + const isAuthRoute = this.reflector.getAllAndOverride(Metadata.AUTH_ROUTE, targets); + const isAdminRoute = this.reflector.getAllAndOverride(Metadata.ADMIN_ROUTE, targets); + const isSharedRoute = this.reflector.getAllAndOverride(Metadata.SHARED_ROUTE, targets); + + if (!isAuthRoute) { + return true; + } + + const req = context.switchToHttp().getRequest(); + + const authDto = await this.authService.validate(req.headers, req.query as Record); + if (!authDto) { + this.logger.warn(`Denied access to authenticated route: ${req.path}`); + return false; + } + + if (authDto.isPublicUser && !isSharedRoute) { + this.logger.warn(`Denied access to non-shared route: ${req.path}`); + return false; + } + + if (isAdminRoute && !authDto.isAdmin) { + this.logger.warn(`Denied access to admin only route: ${req.path}`); + return false; + } + + req.user = authDto; + + return true; + } +} diff --git a/server/src/immich/app.module.ts b/server/src/immich/app.module.ts index f38f0f4d28..10f30a6b21 100644 --- a/server/src/immich/app.module.ts +++ b/server/src/immich/app.module.ts @@ -5,6 +5,7 @@ import { APP_GUARD } from '@nestjs/core'; import { ScheduleModule } from '@nestjs/schedule'; import { AlbumModule } from './api-v1/album/album.module'; import { AssetModule } from './api-v1/asset/asset.module'; +import { AppGuard } from './app.guard'; import { AppService } from './app.service'; import { AlbumController, @@ -23,7 +24,6 @@ import { TagController, UserController, } from './controllers'; -import { AuthGuard } from './middlewares/auth.guard'; @Module({ imports: [ @@ -52,8 +52,8 @@ import { AuthGuard } from './middlewares/auth.guard'; ], providers: [ // - { provide: APP_GUARD, useExisting: AuthGuard }, - AuthGuard, + { provide: APP_GUARD, useExisting: AppGuard }, + AppGuard, AppService, ], }) diff --git a/server/src/immich/app.utils.ts b/server/src/immich/app.utils.ts index 8355964a85..d2d1c24565 100644 --- a/server/src/immich/app.utils.ts +++ b/server/src/immich/app.utils.ts @@ -15,12 +15,29 @@ import { } from '@nestjs/swagger'; import { writeFileSync } from 'fs'; import path from 'path'; -import { Metadata } from './decorators/authenticated.decorator'; + +import { applyDecorators, UsePipes, ValidationPipe } from '@nestjs/common'; +import { Metadata } from './app.guard'; + +export function UseValidation() { + return applyDecorators( + UsePipes( + new ValidationPipe({ + transform: true, + whitelist: true, + }), + ), + ); +} export const asStreamableFile = ({ stream, type, length }: ImmichReadStream) => { return new StreamableFile(stream, { type, length }); }; +export function patchFormData(latin1: string) { + return Buffer.from(latin1, 'latin1').toString('utf8'); +} + function sortKeys(obj: T): T { if (!obj) { return obj; diff --git a/server/src/immich/config/asset-upload.config.spec.ts b/server/src/immich/config/asset-upload.config.spec.ts index 0eb9a31e42..24e7fcb2e3 100644 --- a/server/src/immich/config/asset-upload.config.spec.ts +++ b/server/src/immich/config/asset-upload.config.spec.ts @@ -1,6 +1,6 @@ import { Request } from 'express'; import * as fs from 'fs'; -import { AuthRequest } from '../decorators/auth-user.decorator'; +import { AuthRequest } from '../app.guard'; import { multerUtils } from './asset-upload.config'; const { fileFilter, destination, filename } = multerUtils; diff --git a/server/src/immich/config/asset-upload.config.ts b/server/src/immich/config/asset-upload.config.ts index 714aca0d35..9f934d934b 100644 --- a/server/src/immich/config/asset-upload.config.ts +++ b/server/src/immich/config/asset-upload.config.ts @@ -1,4 +1,4 @@ -import { isSidecarFileType, isSupportedFileType } from '@app/domain'; +import { AuthUserDto, isSidecarFileType, isSupportedFileType } from '@app/domain'; import { StorageCore, StorageFolder } from '@app/domain/storage'; import { BadRequestException, Logger, UnauthorizedException } from '@nestjs/common'; import { MulterOptions } from '@nestjs/platform-express/multer/interfaces/multer-options.interface'; @@ -7,8 +7,8 @@ import { existsSync, mkdirSync } from 'fs'; import { diskStorage, StorageEngine } from 'multer'; import { extname } from 'path'; import sanitize from 'sanitize-filename'; -import { AuthRequest, AuthUserDto } from '../decorators/auth-user.decorator'; -import { patchFormData } from '../utils/path-form-data.util'; +import { AuthRequest } from '../app.guard'; +import { patchFormData } from '../app.utils'; export interface ImmichFile extends Express.Multer.File { /** sha1 hash of file */ diff --git a/server/src/immich/config/profile-image-upload.config.spec.ts b/server/src/immich/config/profile-image-upload.config.spec.ts index 4d2a70653c..06ac6814c7 100644 --- a/server/src/immich/config/profile-image-upload.config.spec.ts +++ b/server/src/immich/config/profile-image-upload.config.spec.ts @@ -1,6 +1,6 @@ import { Request } from 'express'; import * as fs from 'fs'; -import { AuthRequest } from '../decorators/auth-user.decorator'; +import { AuthRequest } from '../app.guard'; import { multerUtils } from './profile-image-upload.config'; const { fileFilter, destination, filename } = multerUtils; diff --git a/server/src/immich/config/profile-image-upload.config.ts b/server/src/immich/config/profile-image-upload.config.ts index fc584a8d76..d56ed1170e 100644 --- a/server/src/immich/config/profile-image-upload.config.ts +++ b/server/src/immich/config/profile-image-upload.config.ts @@ -1,12 +1,12 @@ -import { StorageCore, StorageFolder } from '@app/domain/storage'; +import { AuthUserDto, StorageCore, StorageFolder } from '@app/domain'; import { BadRequestException, UnauthorizedException } from '@nestjs/common'; import { MulterOptions } from '@nestjs/platform-express/multer/interfaces/multer-options.interface'; import { existsSync, mkdirSync } from 'fs'; import { diskStorage } from 'multer'; import { extname } from 'path'; import sanitize from 'sanitize-filename'; -import { AuthRequest, AuthUserDto } from '../decorators/auth-user.decorator'; -import { patchFormData } from '../utils/path-form-data.util'; +import { AuthRequest } from '../app.guard'; +import { patchFormData } from '../app.utils'; export const profileImageUploadOption: MulterOptions = { fileFilter, diff --git a/server/src/immich/controllers/album.controller.ts b/server/src/immich/controllers/album.controller.ts index 37c9752632..6da64dd1ee 100644 --- a/server/src/immich/controllers/album.controller.ts +++ b/server/src/immich/controllers/album.controller.ts @@ -10,9 +10,8 @@ import { GetAlbumsDto } from '@app/domain/album/dto/get-albums.dto'; import { Body, Controller, Delete, Get, Param, Patch, Post, Put, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { ParseMeUUIDPipe } from '../api-v1/validation/parse-me-uuid-pipe'; -import { AuthUser } from '../decorators/auth-user.decorator'; -import { Authenticated } from '../decorators/authenticated.decorator'; -import { UseValidation } from '../decorators/use-validation.decorator'; +import { Authenticated, AuthUser } from '../app.guard'; +import { UseValidation } from '../app.utils'; import { UUIDParamDto } from './dto/uuid-param.dto'; @ApiTags('Album') diff --git a/server/src/immich/controllers/api-key.controller.ts b/server/src/immich/controllers/api-key.controller.ts index 8597d0f7b6..b5814769e5 100644 --- a/server/src/immich/controllers/api-key.controller.ts +++ b/server/src/immich/controllers/api-key.controller.ts @@ -8,9 +8,8 @@ import { } from '@app/domain'; import { Body, Controller, Delete, Get, Param, Post, Put } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { AuthUser } from '../decorators/auth-user.decorator'; -import { Authenticated } from '../decorators/authenticated.decorator'; -import { UseValidation } from '../decorators/use-validation.decorator'; +import { Authenticated, AuthUser } from '../app.guard'; +import { UseValidation } from '../app.utils'; import { UUIDParamDto } from './dto/uuid-param.dto'; @ApiTags('API Key') diff --git a/server/src/immich/controllers/asset.controller.ts b/server/src/immich/controllers/asset.controller.ts index dfea0d5a7e..28e23c98e2 100644 --- a/server/src/immich/controllers/asset.controller.ts +++ b/server/src/immich/controllers/asset.controller.ts @@ -11,10 +11,8 @@ import { MapMarkerDto } from '@app/domain/asset/dto/map-marker.dto'; import { MemoryLaneResponseDto } from '@app/domain/asset/response-dto/memory-lane-response.dto'; import { Body, Controller, Get, HttpCode, HttpStatus, Param, Post, Query, StreamableFile } from '@nestjs/common'; import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; -import { asStreamableFile } from '../app.utils'; -import { AuthUser } from '../decorators/auth-user.decorator'; -import { Authenticated, SharedLinkRoute } from '../decorators/authenticated.decorator'; -import { UseValidation } from '../decorators/use-validation.decorator'; +import { Authenticated, AuthUser, SharedLinkRoute } from '../app.guard'; +import { asStreamableFile, UseValidation } from '../app.utils'; import { UUIDParamDto } from './dto/uuid-param.dto'; @ApiTags('Asset') diff --git a/server/src/immich/controllers/auth.controller.ts b/server/src/immich/controllers/auth.controller.ts index cd0e237393..f695790127 100644 --- a/server/src/immich/controllers/auth.controller.ts +++ b/server/src/immich/controllers/auth.controller.ts @@ -18,9 +18,8 @@ import { import { Body, Controller, Delete, Get, Param, Post, Req, Res } from '@nestjs/common'; import { ApiBadRequestResponse, ApiTags } from '@nestjs/swagger'; import { Request, Response } from 'express'; -import { AuthUser, GetLoginDetails } from '../decorators/auth-user.decorator'; -import { Authenticated, PublicRoute } from '../decorators/authenticated.decorator'; -import { UseValidation } from '../decorators/use-validation.decorator'; +import { Authenticated, AuthUser, GetLoginDetails, PublicRoute } from '../app.guard'; +import { UseValidation } from '../app.utils'; import { UUIDParamDto } from './dto/uuid-param.dto'; @ApiTags('Authentication') diff --git a/server/src/immich/controllers/job.controller.ts b/server/src/immich/controllers/job.controller.ts index b4e3ebed3a..243b7537d2 100644 --- a/server/src/immich/controllers/job.controller.ts +++ b/server/src/immich/controllers/job.controller.ts @@ -1,8 +1,8 @@ import { AllJobStatusResponseDto, JobCommandDto, JobIdParamDto, JobService, JobStatusDto } from '@app/domain'; 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'; +import { Authenticated } from '../app.guard'; +import { UseValidation } from '../app.utils'; @ApiTags('Job') @Controller('jobs') diff --git a/server/src/immich/controllers/oauth.controller.ts b/server/src/immich/controllers/oauth.controller.ts index cb2440a09e..8ec7392688 100644 --- a/server/src/immich/controllers/oauth.controller.ts +++ b/server/src/immich/controllers/oauth.controller.ts @@ -11,9 +11,8 @@ import { import { Body, Controller, Get, HttpStatus, Post, Redirect, Req, Res } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { Request, Response } from 'express'; -import { AuthUser, GetLoginDetails } from '../decorators/auth-user.decorator'; -import { Authenticated, PublicRoute } from '../decorators/authenticated.decorator'; -import { UseValidation } from '../decorators/use-validation.decorator'; +import { Authenticated, AuthUser, GetLoginDetails, PublicRoute } from '../app.guard'; +import { UseValidation } from '../app.utils'; @ApiTags('OAuth') @Controller('oauth') diff --git a/server/src/immich/controllers/partner.controller.ts b/server/src/immich/controllers/partner.controller.ts index 06fa506b99..21fa0deba9 100644 --- a/server/src/immich/controllers/partner.controller.ts +++ b/server/src/immich/controllers/partner.controller.ts @@ -1,9 +1,8 @@ -import { PartnerDirection, PartnerService, UserResponseDto } from '@app/domain'; +import { AuthUserDto, PartnerDirection, PartnerService, UserResponseDto } from '@app/domain'; import { Controller, Delete, Get, Param, Post, Query } from '@nestjs/common'; import { ApiQuery, ApiTags } from '@nestjs/swagger'; -import { AuthUser, AuthUserDto } from '../decorators/auth-user.decorator'; -import { Authenticated } from '../decorators/authenticated.decorator'; -import { UseValidation } from '../decorators/use-validation.decorator'; +import { Authenticated, AuthUser } from '../app.guard'; +import { UseValidation } from '../app.utils'; import { UUIDParamDto } from './dto/uuid-param.dto'; @ApiTags('Partner') diff --git a/server/src/immich/controllers/person.controller.ts b/server/src/immich/controllers/person.controller.ts index 26cd2e62ca..5752304598 100644 --- a/server/src/immich/controllers/person.controller.ts +++ b/server/src/immich/controllers/person.controller.ts @@ -8,9 +8,8 @@ import { } from '@app/domain'; import { Body, Controller, Get, Param, Put, StreamableFile } from '@nestjs/common'; import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; -import { AuthUser } from '../decorators/auth-user.decorator'; -import { Authenticated } from '../decorators/authenticated.decorator'; -import { UseValidation } from '../decorators/use-validation.decorator'; +import { Authenticated, AuthUser } from '../app.guard'; +import { UseValidation } from '../app.utils'; import { UUIDParamDto } from './dto/uuid-param.dto'; function asStreamableFile({ stream, type, length }: ImmichReadStream) { diff --git a/server/src/immich/controllers/search.controller.ts b/server/src/immich/controllers/search.controller.ts index 4c6062131b..bbc10d9bbe 100644 --- a/server/src/immich/controllers/search.controller.ts +++ b/server/src/immich/controllers/search.controller.ts @@ -8,9 +8,8 @@ import { } from '@app/domain'; import { Controller, Get, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { AuthUser } from '../decorators/auth-user.decorator'; -import { Authenticated } from '../decorators/authenticated.decorator'; -import { UseValidation } from '../decorators/use-validation.decorator'; +import { Authenticated, AuthUser } from '../app.guard'; +import { UseValidation } from '../app.utils'; @ApiTags('Search') @Controller('search') diff --git a/server/src/immich/controllers/server-info.controller.ts b/server/src/immich/controllers/server-info.controller.ts index 7819e2bd36..e98485cbe8 100644 --- a/server/src/immich/controllers/server-info.controller.ts +++ b/server/src/immich/controllers/server-info.controller.ts @@ -7,8 +7,8 @@ import { } from '@app/domain'; import { Controller, Get } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { AdminRoute, Authenticated, PublicRoute } from '../decorators/authenticated.decorator'; -import { UseValidation } from '../decorators/use-validation.decorator'; +import { AdminRoute, Authenticated, PublicRoute } from '../app.guard'; +import { UseValidation } from '../app.utils'; @ApiTags('Server Info') @Controller('server-info') diff --git a/server/src/immich/controllers/shared-link.controller.ts b/server/src/immich/controllers/shared-link.controller.ts index aa0e533975..68a4a1b786 100644 --- a/server/src/immich/controllers/shared-link.controller.ts +++ b/server/src/immich/controllers/shared-link.controller.ts @@ -9,9 +9,8 @@ import { } from '@app/domain'; import { Body, Controller, Delete, Get, Param, Patch, Post, Put } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { AuthUser } from '../decorators/auth-user.decorator'; -import { Authenticated, SharedLinkRoute } from '../decorators/authenticated.decorator'; -import { UseValidation } from '../decorators/use-validation.decorator'; +import { Authenticated, AuthUser, SharedLinkRoute } from '../app.guard'; +import { UseValidation } from '../app.utils'; import { UUIDParamDto } from './dto/uuid-param.dto'; @ApiTags('Shared Link') diff --git a/server/src/immich/controllers/system-config.controller.ts b/server/src/immich/controllers/system-config.controller.ts index fb7e38b362..646c9a629c 100644 --- a/server/src/immich/controllers/system-config.controller.ts +++ b/server/src/immich/controllers/system-config.controller.ts @@ -1,8 +1,8 @@ import { SystemConfigDto, SystemConfigService, SystemConfigTemplateStorageOptionDto } from '@app/domain'; 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'; +import { Authenticated } from '../app.guard'; +import { UseValidation } from '../app.utils'; @ApiTags('System Config') @Controller('system-config') diff --git a/server/src/immich/controllers/tag.controller.ts b/server/src/immich/controllers/tag.controller.ts index 7d23e2bf77..d949a42745 100644 --- a/server/src/immich/controllers/tag.controller.ts +++ b/server/src/immich/controllers/tag.controller.ts @@ -2,6 +2,7 @@ import { AssetIdsDto, AssetIdsResponseDto, AssetResponseDto, + AuthUserDto, CreateTagDto, TagResponseDto, TagService, @@ -9,9 +10,8 @@ import { } from '@app/domain'; import { Body, Controller, Delete, Get, Param, Patch, Post, Put } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { AuthUser, AuthUserDto } from '../decorators/auth-user.decorator'; -import { Authenticated } from '../decorators/authenticated.decorator'; -import { UseValidation } from '../decorators/use-validation.decorator'; +import { Authenticated, AuthUser } from '../app.guard'; +import { UseValidation } from '../app.utils'; import { UUIDParamDto } from './dto/uuid-param.dto'; @ApiTags('Tag') diff --git a/server/src/immich/controllers/user.controller.ts b/server/src/immich/controllers/user.controller.ts index 1a4b3faf19..52b00898e1 100644 --- a/server/src/immich/controllers/user.controller.ts +++ b/server/src/immich/controllers/user.controller.ts @@ -1,4 +1,5 @@ import { + AuthUserDto, CreateProfileImageDto, CreateProfileImageResponseDto, CreateUserDto, @@ -27,10 +28,9 @@ import { import { FileInterceptor } from '@nestjs/platform-express'; import { ApiBody, ApiConsumes, ApiTags } from '@nestjs/swagger'; import { Response as Res } from 'express'; +import { AdminRoute, Authenticated, AuthUser, PublicRoute } from '../app.guard'; +import { UseValidation } from '../app.utils'; import { profileImageUploadOption } from '../config/profile-image-upload.config'; -import { AuthUser, AuthUserDto } from '../decorators/auth-user.decorator'; -import { AdminRoute, Authenticated, PublicRoute } from '../decorators/authenticated.decorator'; -import { UseValidation } from '../decorators/use-validation.decorator'; @ApiTags('User') @Controller('user') diff --git a/server/src/immich/decorators/auth-user.decorator.ts b/server/src/immich/decorators/auth-user.decorator.ts deleted file mode 100644 index adda34dd5a..0000000000 --- a/server/src/immich/decorators/auth-user.decorator.ts +++ /dev/null @@ -1,25 +0,0 @@ -export { AuthUserDto } from '@app/domain'; -import { AuthUserDto, LoginDetails } from '@app/domain'; -import { createParamDecorator, ExecutionContext } from '@nestjs/common'; -import { Request } from 'express'; -import { UAParser } from 'ua-parser-js'; - -export interface AuthRequest extends Request { - user?: AuthUserDto; -} - -export const AuthUser = createParamDecorator((data, ctx: ExecutionContext): AuthUserDto => { - return ctx.switchToHttp().getRequest<{ user: AuthUserDto }>().user; -}); - -export const GetLoginDetails = createParamDecorator((data, ctx: ExecutionContext): LoginDetails => { - const req = ctx.switchToHttp().getRequest(); - const userAgent = UAParser(req.headers['user-agent']); - - return { - clientIp: req.clientIp, - isSecure: req.secure, - deviceType: userAgent.browser.name || userAgent.device.type || req.headers.devicemodel || '', - deviceOS: userAgent.os.name || req.headers.devicetype || '', - }; -}); diff --git a/server/src/immich/decorators/authenticated.decorator.ts b/server/src/immich/decorators/authenticated.decorator.ts deleted file mode 100644 index 68a01787c0..0000000000 --- a/server/src/immich/decorators/authenticated.decorator.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { IMMICH_API_KEY_NAME } from '@app/domain'; -import { applyDecorators, SetMetadata } from '@nestjs/common'; -import { ApiBearerAuth, ApiCookieAuth, ApiQuery, ApiSecurity } from '@nestjs/swagger'; - -interface AuthenticatedOptions { - admin?: boolean; - isShared?: boolean; -} - -export enum Metadata { - AUTH_ROUTE = 'auth_route', - ADMIN_ROUTE = 'admin_route', - SHARED_ROUTE = 'shared_route', - PUBLIC_SECURITY = 'public_security', -} - -const adminDecorator = SetMetadata(Metadata.ADMIN_ROUTE, true); - -const sharedLinkDecorators = [ - SetMetadata(Metadata.SHARED_ROUTE, true), - ApiQuery({ name: 'key', type: String, required: false }), -]; - -export const Authenticated = (options: AuthenticatedOptions = {}) => { - const decorators: MethodDecorator[] = [ - ApiBearerAuth(), - ApiCookieAuth(), - ApiSecurity(IMMICH_API_KEY_NAME), - SetMetadata(Metadata.AUTH_ROUTE, true), - ]; - - if (options.admin) { - decorators.push(adminDecorator); - } - - if (options.isShared) { - decorators.push(...sharedLinkDecorators); - } - - return applyDecorators(...decorators); -}; - -export const PublicRoute = () => - applyDecorators(SetMetadata(Metadata.AUTH_ROUTE, false), ApiSecurity(Metadata.PUBLIC_SECURITY)); -export const SharedLinkRoute = () => applyDecorators(...sharedLinkDecorators); -export const AdminRoute = () => adminDecorator; diff --git a/server/src/immich/decorators/use-validation.decorator.ts b/server/src/immich/decorators/use-validation.decorator.ts deleted file mode 100644 index a9b40ea40f..0000000000 --- a/server/src/immich/decorators/use-validation.decorator.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { applyDecorators, UsePipes, ValidationPipe } from '@nestjs/common'; - -export function UseValidation() { - return applyDecorators( - UsePipes( - new ValidationPipe({ - transform: true, - whitelist: true, - }), - ), - ); -} diff --git a/server/src/immich/decorators/validate-uuid.decorator.ts b/server/src/immich/decorators/validate-uuid.decorator.ts deleted file mode 100644 index 16a2e1ad05..0000000000 --- a/server/src/immich/decorators/validate-uuid.decorator.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { applyDecorators } from '@nestjs/common'; -import { ApiProperty } from '@nestjs/swagger'; -import { IsArray, IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator'; - -export type Options = { - optional?: boolean; - each?: boolean; -}; - -export function ValidateUUID({ optional, each }: Options = { optional: false, each: false }) { - return applyDecorators( - IsUUID('4', { each }), - ApiProperty({ format: 'uuid' }), - optional ? IsOptional() : IsNotEmpty(), - each ? IsArray() : IsString(), - ); -} diff --git a/server/src/immich/middlewares/auth.guard.ts b/server/src/immich/middlewares/auth.guard.ts deleted file mode 100644 index 7d3204ed4e..0000000000 --- a/server/src/immich/middlewares/auth.guard.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { AuthService } from '@app/domain'; -import { CanActivate, ExecutionContext, Injectable, Logger } from '@nestjs/common'; -import { Reflector } from '@nestjs/core'; -import { AuthRequest } from '../decorators/auth-user.decorator'; -import { Metadata } from '../decorators/authenticated.decorator'; - -@Injectable() -export class AuthGuard implements CanActivate { - private logger = new Logger(AuthGuard.name); - - constructor(private reflector: Reflector, private authService: AuthService) {} - - async canActivate(context: ExecutionContext): Promise { - const targets = [context.getHandler(), context.getClass()]; - - const isAuthRoute = this.reflector.getAllAndOverride(Metadata.AUTH_ROUTE, targets); - const isAdminRoute = this.reflector.getAllAndOverride(Metadata.ADMIN_ROUTE, targets); - const isSharedRoute = this.reflector.getAllAndOverride(Metadata.SHARED_ROUTE, targets); - - if (!isAuthRoute) { - return true; - } - - const req = context.switchToHttp().getRequest(); - - const authDto = await this.authService.validate(req.headers, req.query as Record); - if (!authDto) { - this.logger.warn(`Denied access to authenticated route: ${req.path}`); - return false; - } - - if (authDto.isPublicUser && !isSharedRoute) { - this.logger.warn(`Denied access to non-shared route: ${req.path}`); - return false; - } - - if (isAdminRoute && !authDto.isAdmin) { - this.logger.warn(`Denied access to admin only route: ${req.path}`); - return false; - } - - req.user = authDto; - - return true; - } -} diff --git a/server/src/immich/utils/path-form-data.util.ts b/server/src/immich/utils/path-form-data.util.ts deleted file mode 100644 index 4ab7d56c50..0000000000 --- a/server/src/immich/utils/path-form-data.util.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function patchFormData(latin1: string) { - return Buffer.from(latin1, 'latin1').toString('utf8'); -} diff --git a/server/src/immich/utils/transform.util.ts b/server/src/immich/utils/transform.util.ts deleted file mode 100644 index b03ce6a2d8..0000000000 --- a/server/src/immich/utils/transform.util.ts +++ /dev/null @@ -1,18 +0,0 @@ -import sanitize from 'sanitize-filename'; - -interface IValue { - value?: string; -} - -export const toBoolean = ({ value }: IValue) => { - if (value == 'true') { - return true; - } else if (value == 'false') { - return false; - } - return value; -}; - -export const toEmail = ({ value }: IValue) => value?.toLowerCase(); - -export const toSanitized = ({ value }: IValue) => sanitize((value || '').replace(/\./g, '')); diff --git a/server/test/test-utils.ts b/server/test/test-utils.ts index 5736a20775..58492a011c 100644 --- a/server/test/test-utils.ts +++ b/server/test/test-utils.ts @@ -1,5 +1,5 @@ -import { AuthUserDto } from '@app/immich/decorators/auth-user.decorator'; -import { AuthGuard } from '@app/immich/middlewares/auth.guard'; +import { AuthUserDto } from '@app/domain'; +import { AppGuard } from '@app/immich/app.guard'; import { CanActivate, ExecutionContext } from '@nestjs/common'; import { TestingModuleBuilder } from '@nestjs/testing'; import { DataSource } from 'typeorm'; @@ -34,5 +34,5 @@ export function authCustom(builder: TestingModuleBuilder, callback: CustomAuthCa return true; }, }; - return builder.overrideProvider(AuthGuard).useValue(canActivate); + return builder.overrideProvider(AppGuard).useValue(canActivate); }