diff --git a/lib/security/rate-limiter.ts b/lib/security/rate-limiter.ts index 7bf60b75d..d4fdf7683 100644 --- a/lib/security/rate-limiter.ts +++ b/lib/security/rate-limiter.ts @@ -77,6 +77,9 @@ export const rateLimiters = { // 通用API限制:每分钟100次请求 api: new RateLimiter(60000, 100), + // 管理后台接口:设置页会并发加载大量配置,不能和公开 API 共用低阈值 + admin: new RateLimiter(60000, 3000), + // 文件上传接口:每分钟60次请求(独立于通用API限流) upload: new RateLimiter(60000, 60), diff --git a/proxy.ts b/proxy.ts index 006be67d9..e0c9f5f72 100644 --- a/proxy.ts +++ b/proxy.ts @@ -191,6 +191,30 @@ export async function proxy(request: NextRequest) { { status: 429 } ); } + } else if (pathname.startsWith("/api/admin/")) { + const token = await resolveToken(); + const adminIdentifier = + typeof token?.sub === "string" && token.sub.length > 0 + ? `admin:${token.sub}` + : `admin-ip:${clientIP}`; + const adminRateLimit = checkRateLimitByIdentifier( + adminIdentifier, + rateLimiters.admin + ); + if (!adminRateLimit.allowed) { + const retryAfter = Math.max( + 1, + Math.ceil((adminRateLimit.resetTime - Date.now()) / 1000) + ); + console.warn(`[Security] Admin rate limit exceeded for IP: ${clientIP}`, { + pathname, + resetTime: new Date(adminRateLimit.resetTime).toISOString(), + }); + return NextResponse.json( + { error: "Too many admin requests", retryAfter }, + { status: 429, headers: { "Retry-After": String(retryAfter) } } + ); + } } else if (pathname.startsWith("/api/")) { const rateLimit = checkRateLimit(request, rateLimiters.api); if (!rateLimit.allowed) {