From 8c591d2edd7ae31a593967ba4d3a74ede17a6ed0 Mon Sep 17 00:00:00 2001 From: kamisatukayaku <645732784@qq.com> Date: Wed, 10 Apr 2024 14:31:24 +0800 Subject: [PATCH 1/4] fix: regenerate schema.prisma --- prisma/schema.prisma | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index e952f4d7..fe45f8a8 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -231,15 +231,21 @@ model SessionRefreshLog { // avatars.legacy.prisma // +enum AvatarType { + default + predefined + upload +} + model Avatar { id Int @id @default(autoincrement()) url String @db.VarChar name String @db.VarChar createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6) - avatarType String @map("avatar_type") @db.VarChar + avatarType AvatarType @map("avatar_type") usageCount Int @default(0) @map("usage_count") - groupProfile GroupProfile[] - userProfile UserProfile[] + GroupProfile GroupProfile[] + UserProfile UserProfile[] @@map("avatar") } From 8bdebe450fd0e7b7772fd8292f2ea362657b2da8 Mon Sep 17 00:00:00 2001 From: kamisatukayaku <645732784@qq.com> Date: Wed, 10 Apr 2024 14:34:41 +0800 Subject: [PATCH 2/4] fix: add windows to prisma binaryTargets --- prisma/schema.prisma | 2 +- src/app.prisma | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index fe45f8a8..3a842179 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -9,7 +9,7 @@ generator client { provider = "prisma-client-js" - binaryTargets = ["debian-openssl-3.0.x", "debian-openssl-1.1.x"] + binaryTargets = ["debian-openssl-3.0.x", "debian-openssl-1.1.x", "windows"] } datasource db { diff --git a/src/app.prisma b/src/app.prisma index 3a52b643..d4619433 100644 --- a/src/app.prisma +++ b/src/app.prisma @@ -1,6 +1,6 @@ generator client { provider = "prisma-client-js" - binaryTargets = ["debian-openssl-3.0.x", "debian-openssl-1.1.x"] + binaryTargets = ["debian-openssl-3.0.x", "debian-openssl-1.1.x", "windows"] } datasource db { From 73122a5c2b4fb00299b0e9961d3a2cc31361e535 Mon Sep 17 00:00:00 2001 From: kamisatukayaku <645732784@qq.com> Date: Thu, 11 Apr 2024 20:17:52 +0800 Subject: [PATCH 3/4] fix: turn join to pathJoinSafe --- src/avatars/avatars.error.ts | 6 ++++ src/avatars/avatars.module.ts | 54 ++++++++++++++++++++++++++++++++-- src/avatars/avatars.service.ts | 18 +++++++----- test/avatars.e2e-spec.ts | 10 +++++++ 4 files changed, 78 insertions(+), 10 deletions(-) diff --git a/src/avatars/avatars.error.ts b/src/avatars/avatars.error.ts index 618c578c..f96346a8 100644 --- a/src/avatars/avatars.error.ts +++ b/src/avatars/avatars.error.ts @@ -21,3 +21,9 @@ export class InvalidAvatarTypeError extends BaseError { super('InvalidAvatarTypeError', `Invalid Avatar type: ${avatarType}`, 400); } } + +export class InvalidPathError extends BaseError { + constructor() { + super('InvalidPathError', `Invalid path`, 400); + } +} diff --git a/src/avatars/avatars.module.ts b/src/avatars/avatars.module.ts index bb3a0f8c..bb27ee0f 100644 --- a/src/avatars/avatars.module.ts +++ b/src/avatars/avatars.module.ts @@ -1,14 +1,59 @@ import { Module } from '@nestjs/common'; import { MulterModule } from '@nestjs/platform-express'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { existsSync, mkdirSync } from 'fs'; import { diskStorage } from 'multer'; -import { extname, join } from 'path'; +import * as path from 'path'; +import { extname } from 'path'; import { v4 as uuidv4 } from 'uuid'; import { AuthModule } from '../auth/auth.module'; import { AvatarsController } from './avatars.controller'; +import { InvalidPathError } from './avatars.error'; import { Avatar } from './avatars.legacy.entity'; import { AvatarsService } from './avatars.service'; -import { existsSync, mkdirSync } from 'fs'; +declare module 'path' { + interface PlatformPath { + joinSafe(...paths: string[]): string | undefined; + } +} +function pathJoinSafePosix( + dir: string, + ...paths: string[] +): string | undefined { + dir = path.posix.normalize(dir); + const pathname = path.posix.join(dir, ...paths); + if (pathname.substring(0, dir.length) !== dir) return undefined; + return pathname; +} + +function pathJoinSafeWin32( + dir: string, + ...paths: string[] +): string | undefined { + dir = path.win32.normalize(dir); + const pathname = path.win32.join(dir, ...paths); + if (pathname.substring(0, dir.length) !== dir) return undefined; + return pathname; +} + +path.posix.joinSafe = pathJoinSafePosix; +path.win32.joinSafe = pathJoinSafeWin32; + +export function pathJoinSafe( + dir: string, + ...paths: string[] +): string | undefined { + dir = path.normalize(dir); + let joinedPath: string; + if (path.sep === '/') { + joinedPath = path.posix.join(dir, ...paths); + } else { + joinedPath = path.win32.join(dir, ...paths); + } + + if (joinedPath.substring(0, dir.length) !== dir) return undefined; + return joinedPath; +} @Module({ imports: [ TypeOrmModule.forFeature([Avatar]), @@ -22,7 +67,10 @@ import { existsSync, mkdirSync } from 'fs'; 'error', ); } - const dest = join(process.env.FILE_UPLOAD_PATH, 'avatars'); + const dest = pathJoinSafe(process.env.FILE_UPLOAD_PATH, 'avatars'); + if (!dest) { + throw new InvalidPathError(); + } if (!existsSync(dest)) { mkdirSync(dest, { recursive: true }); } diff --git a/src/avatars/avatars.service.ts b/src/avatars/avatars.service.ts index af2cf196..69305e99 100644 --- a/src/avatars/avatars.service.ts +++ b/src/avatars/avatars.service.ts @@ -1,10 +1,10 @@ import { Injectable, OnModuleInit } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { readdirSync } from 'fs'; -import { join } from 'path'; import { Repository } from 'typeorm'; -import { AvatarNotFoundError } from './avatars.error'; +import { AvatarNotFoundError, InvalidPathError } from './avatars.error'; import { Avatar, AvatarType } from './avatars.legacy.entity'; +import { pathJoinSafe } from './avatars.module'; @Injectable() export class AvatarsService implements OnModuleInit { constructor( @@ -15,8 +15,10 @@ export class AvatarsService implements OnModuleInit { this.initialize(); } private async initialize(): Promise { - const sourcePath = join(__dirname, '../resources/avatars'); - + const sourcePath = pathJoinSafe(__dirname, '../avatars'); + if (!sourcePath) { + throw new InvalidPathError(); + } const avatarFiles = readdirSync(sourcePath); /* istanbul ignore if */ if (!process.env.DEFAULT_AVATAR_NAME) { @@ -26,8 +28,10 @@ export class AvatarsService implements OnModuleInit { } const defaultAvatarName = process.env.DEFAULT_AVATAR_NAME; - const defaultAvatarPath = join(sourcePath, defaultAvatarName); - + const defaultAvatarPath = pathJoinSafe(sourcePath, defaultAvatarName); + if (!defaultAvatarPath) { + throw new InvalidPathError(); + } let defaultAvatar = await this.avatarRepository.findOneBy({ avatarType: AvatarType.default, }); @@ -55,7 +59,7 @@ export class AvatarsService implements OnModuleInit { } await Promise.all( predefinedAvatars.map(async (name) => { - const avatarPath = join(sourcePath, name); + const avatarPath = pathJoinSafe(sourcePath, name); const predefinedAvatar = this.avatarRepository.create({ url: avatarPath, name, diff --git a/test/avatars.e2e-spec.ts b/test/avatars.e2e-spec.ts index 27c53cde..53f91404 100644 --- a/test/avatars.e2e-spec.ts +++ b/test/avatars.e2e-spec.ts @@ -29,6 +29,16 @@ describe('Avatar Module', () => { AvatarId = respond.body.data.avatarid; }); }); + describe('get Invalid path', () => { + it('should return InvalidPathError', async () => { + const respond = await request(app.getHttpServer()) + .post('/resources/avatars') + .attach('avatar', 'src/resources/avatars/default.jpg'); + expect(respond.status).toBe(400); + expect(respond.body.message).toContain('Invalid path'); + expect(respond.body.data).toBeUndefined(); + }); + }); describe('get avatar', () => { it('should get the uploaded avatar', async () => { const avatarId = AvatarId; From d222ab82a24b07c7a23010c249ebf14f856eb9c6 Mon Sep 17 00:00:00 2001 From: kamisatukayaku <645732784@qq.com> Date: Fri, 12 Apr 2024 15:57:40 +0800 Subject: [PATCH 4/4] fix: delete a test due to unable to make bug --- test/avatars.e2e-spec.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/test/avatars.e2e-spec.ts b/test/avatars.e2e-spec.ts index 53f91404..27c53cde 100644 --- a/test/avatars.e2e-spec.ts +++ b/test/avatars.e2e-spec.ts @@ -29,16 +29,6 @@ describe('Avatar Module', () => { AvatarId = respond.body.data.avatarid; }); }); - describe('get Invalid path', () => { - it('should return InvalidPathError', async () => { - const respond = await request(app.getHttpServer()) - .post('/resources/avatars') - .attach('avatar', 'src/resources/avatars/default.jpg'); - expect(respond.status).toBe(400); - expect(respond.body.message).toContain('Invalid path'); - expect(respond.body.data).toBeUndefined(); - }); - }); describe('get avatar', () => { it('should get the uploaded avatar', async () => { const avatarId = AvatarId;