fix: 修复代码审查发现的安全性和性能问题
主要修复: - 安全性:API 密钥使用恒定时间比较防止时序攻击 - 安全性:URL 验证仅允许 http/https 协议,防止 XSS - 安全性:移除 Markdown 的 rehypeRaw 插件增强 XSS 防护 - 安全性:Webhook 错误信息脱敏,生产环境不暴露敏感数据 - 性能:项目详情页使用并行数据获取 - 性能:Webhook 批量查询标签,避免 N+1 查询问题 - 类型安全:移除 any 类型,使用 Prisma 类型注解 - 类型安全:为 useProjects 函数添加返回类型 - 代码质量:创建 slug 工具函数统一 slug 生成逻辑 - 代码质量:搜索字符串添加最小长度限制(2字符) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,25 @@
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import type { Prisma } from '@prisma/client'
|
||||
|
||||
// 定义带有标签和链接的项目类型
|
||||
export type ProjectWithTagsAndLinks = Prisma.ProjectGetPayload<{
|
||||
include: {
|
||||
tags: { include: { tag: true } }
|
||||
links: true
|
||||
}
|
||||
}>
|
||||
|
||||
// 定义扁平化标签的项目类型
|
||||
export type ProjectWithFlatTags = Omit<ProjectWithTagsAndLinks, 'tags'> & {
|
||||
tags: Prisma.TagGetPayload<{}>[]
|
||||
}
|
||||
|
||||
// 定义标签计数类型
|
||||
export type TagWithProjectCount = Prisma.TagGetPayload<{
|
||||
include: {
|
||||
_count: { select: { projects: true } }
|
||||
}
|
||||
}>
|
||||
|
||||
export async function getProjects(options?: {
|
||||
search?: string
|
||||
@@ -6,7 +27,15 @@ export async function getProjects(options?: {
|
||||
status?: 'ACTIVE' | 'ARCHIVED'
|
||||
page?: number
|
||||
limit?: number
|
||||
}) {
|
||||
}): Promise<{
|
||||
projects: ProjectWithFlatTags[]
|
||||
pagination: {
|
||||
page: number
|
||||
limit: number
|
||||
total: number
|
||||
totalPages: number
|
||||
}
|
||||
}> {
|
||||
const {
|
||||
search,
|
||||
tag,
|
||||
@@ -15,11 +44,12 @@ export async function getProjects(options?: {
|
||||
limit = 20,
|
||||
} = options || {}
|
||||
|
||||
const where: any = {
|
||||
const where: Prisma.ProjectWhereInput = {
|
||||
status,
|
||||
}
|
||||
|
||||
if (search) {
|
||||
// 添加搜索字符串长度验证
|
||||
if (search && search.length >= 2 && search.length <= 100) {
|
||||
where.OR = [
|
||||
{ name: { contains: search, mode: 'insensitive' } },
|
||||
{ nameEn: { contains: search, mode: 'insensitive' } },
|
||||
@@ -31,7 +61,9 @@ export async function getProjects(options?: {
|
||||
if (tag) {
|
||||
where.tags = {
|
||||
some: {
|
||||
slug: tag,
|
||||
tag: {
|
||||
slug: tag,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -73,7 +105,7 @@ export async function getProjects(options?: {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getProjectBySlug(slug: string) {
|
||||
export async function getProjectBySlug(slug: string): Promise<ProjectWithFlatTags | null> {
|
||||
const project = await prisma.project.findUnique({
|
||||
where: { slug },
|
||||
include: {
|
||||
@@ -97,7 +129,7 @@ export async function getProjectBySlug(slug: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAllTags() {
|
||||
export async function getAllTags(): Promise<TagWithProjectCount[]> {
|
||||
return prisma.tag.findMany({
|
||||
include: {
|
||||
_count: {
|
||||
@@ -110,7 +142,7 @@ export async function getAllTags() {
|
||||
})
|
||||
}
|
||||
|
||||
export async function getTagsWithProjectCounts() {
|
||||
export async function getTagsWithProjectCounts(): Promise<TagWithProjectCount[]> {
|
||||
const tags = await prisma.tag.findMany({
|
||||
include: {
|
||||
_count: {
|
||||
|
||||
Reference in New Issue
Block a user