修复 Next.js 16.1.1 兼容性:合并 middleware.ts 到 proxy.ts

Next.js 16.1.1 不再允许同时存在 middleware.ts 和 proxy.ts 文件。
已将 middleware.ts 的所有安全功能(IP黑名单、速率限制、可疑请求检测等)合并到 proxy.ts 中,并删除 middleware.ts。

此更改不影响功能,只是符合 Next.js 新版本的架构要求。
This commit is contained in:
Your Name 2026-01-01 12:32:06 +08:00
parent fe6a720de8
commit 202f159f8f

View File

@ -1,17 +1,79 @@
import { type NextRequest, NextResponse } from "next/server";
import { getToken } from "next-auth/jwt";
import { isBlockedIP, getClientIP } from "@/lib/security/ip-blocklist";
import { checkRateLimit, rateLimiters } from "@/lib/security/rate-limiter";
import { isSuspiciousRequest, logSuspiciousRequest } from "@/lib/security/request-monitor";
export async function proxy(request: NextRequest) {
const { pathname, search } = request.nextUrl;
const { pathname } = request.nextUrl;
if (pathname.startsWith("/ping")) {
return new Response("pong", { status: 200 });
// 🛡️ 安全检查1:阻止黑名单IP
const clientIP = getClientIP(request);
if (isBlockedIP(clientIP)) {
console.warn(`[Security] Blocked request from blacklisted IP: ${clientIP}`, {
pathname,
method: request.method,
userAgent: request.headers.get("user-agent"),
});
return new NextResponse("Forbidden", { status: 403 });
}
// 🛡️ 安全检查2:检测可疑请求模式
const suspiciousCheck = isSuspiciousRequest(request);
if (suspiciousCheck.suspicious) {
logSuspiciousRequest(request, suspiciousCheck.reason || "Unknown suspicious pattern");
// 对于可疑请求,记录但不立即阻止(避免误报)
// 可以在这里添加更严格的检查
}
// 🛡️ 安全检查3:速率限制
// 对API路径应用速率限制
if (pathname.startsWith("/api/")) {
const rateLimit = checkRateLimit(request, rateLimiters.api);
if (!rateLimit.allowed) {
console.warn(`[Security] Rate limit exceeded for IP: ${clientIP}`, {
pathname,
resetTime: new Date(rateLimit.resetTime).toISOString(),
});
return NextResponse.json(
{ error: "Too many requests", retryAfter: Math.ceil((rateLimit.resetTime - Date.now()) / 1000) },
{ status: 429 }
);
}
}
// 🛡️ 安全检查4:认证相关API的严格速率限制
if (pathname.startsWith("/api/auth/")) {
const rateLimit = checkRateLimit(request, rateLimiters.auth);
if (!rateLimit.allowed) {
console.warn(`[Security] Auth rate limit exceeded for IP: ${clientIP}`);
return NextResponse.json(
{ error: "Too many authentication attempts", retryAfter: Math.ceil((rateLimit.resetTime - Date.now()) / 1000) },
{ status: 429 }
);
}
}
// 允许访问 ping 端点
if (pathname.startsWith("/ping")) {
return NextResponse.next();
}
// 允许访问认证相关 API
if (pathname.startsWith("/api/auth")) {
return NextResponse.next();
}
// 允许访问公开的网站设置 API(登录页面需要)
if (pathname === "/api/site-settings") {
return NextResponse.next();
}
// 允许访问钉钉回调页面(public 文件夹中的文件)
if (pathname === "/dingtalk-callback.html") {
return NextResponse.next();
}
// 检测是否为 HTTPS(通过 AUTH_URL 或请求协议判断)
const isSecure = process.env.AUTH_URL?.startsWith("https://") ||
request.nextUrl.protocol === "https:";
@ -28,7 +90,18 @@ export async function proxy(request: NextRequest) {
// 未登录用户只能访问 /login 和 /register
if (!token && !["/login", "/register"].includes(pathname)) {
// 对于 API 路由的非 GET 请求,直接返回 401 JSON 响应(不重定向)
if (pathname.startsWith("/api/") && request.method !== "GET") {
return NextResponse.json(
{ error: "未授权,请先登录" },
{ status: 401 }
);
}
// 对于页面路由(GET 请求),重定向到登录页面
const loginUrl = new URL("/login", externalBase);
// 保存当前路径作为回调 URL
loginUrl.searchParams.set("callbackUrl", pathname);
return NextResponse.redirect(loginUrl);
}
@ -42,10 +115,14 @@ export async function proxy(request: NextRequest) {
export const config = {
matcher: [
"/",
"/chat/:id",
"/api/:path*",
"/login",
"/register",
/*
* 匹配所有请求路径,除了:
* - api/auth (NextAuth.js)
* - _next/static (静态文件)
* - _next/image (图片优化文件)
* - favicon.ico (favicon文件)
* - public 文件夹中的文件
*/
"/((?!api/auth|_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
],
};