api-server/src/common/utils/security.util.ts

81 lines
2.2 KiB
TypeScript

import { BadRequestException, ForbiddenException } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
type Delegate = {
findUnique(args: { where: any }): Promise<any>;
findFirst(args: { where: any }): Promise<any>;
};
export async function findByIdAndUserId<T extends Delegate>(
delegate: T,
id: number | bigint,
userId: number | bigint,
resourceName: string,
) {
const record = await delegate.findUnique({ where: { id } } as any);
if (!record) {
throw new BadRequestException(`${resourceName}不存在`);
}
if (record.userId !== userId) {
throw new ForbiddenException(`无权访问该${resourceName}`);
}
return record;
}
export function ensureOwnership(
record: any,
userId: number | bigint,
resourceName: string,
) {
if (!record) {
throw new BadRequestException(`${resourceName}不存在`);
}
if (record.userId !== userId) {
throw new ForbiddenException(`无权访问该${resourceName}`);
}
return record;
}
export function sanitizeFilename(originalName: string): string {
const ext = originalName.split('.').pop()?.toLowerCase() || '';
const safeExt = ext.replace(/[^a-z0-9]/g, '');
const randomName =
Date.now().toString(36) + Math.random().toString(36).substring(2, 15);
return `${randomName}.${safeExt}`;
}
export const ALLOWED_FILE_TYPES = [
'application/pdf',
'text/plain',
'text/markdown',
'text/csv',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'image/png',
'image/jpeg',
'image/webp',
];
export const MAX_FILE_SIZE = 20 * 1024 * 1024;
export function validateFileUpload(
mimeType: string,
sizeBytes: number,
): void {
if (!ALLOWED_FILE_TYPES.includes(mimeType)) {
throw new BadRequestException(
`不支持的文件类型: ${mimeType},仅支持 PDF/Word/Excel/文本/图片`,
);
}
if (sizeBytes > MAX_FILE_SIZE) {
throw new BadRequestException(
`文件大小不能超过 ${MAX_FILE_SIZE / 1024 / 1024}MB`,
);
}
}
export function maskSecret(secret: string): string {
if (!secret || secret.length < 8) return '***';
return secret.slice(0, 4) + '***' + secret.slice(-4);
}