Files
2026-08-21 16:00:13 +08:00

74 lines
2.1 KiB
TypeScript

/**
* 根级别 Middleware — 全局 cookie 存在性校验。
*
* 运行于 Edge Runtime,只能使用 request.cookies 等 Edge 兼容 API。
* 不做 token 有效性验证(那需要 fetch 后端,会阻塞每个请求),
* 只检查 hunjing-access cookie 是否存在。
*
* 未认证的 API 请求 → 返回 401 JSON
* 未认证的页面请求 → 重定向 /login
* 公开路径(/login、/_next/*、/favicon.ico 等)→ 放行
*/
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export const config = {
matcher: [
// 仅匹配需要登录的路径:API 路由和受保护的页面
"/api/proxy/:path*",
"/api/sse/:path*",
"/api/auth/me",
"/api/auth/refresh",
"/chat/:path*",
"/memory/:path*",
"/characters/:path*",
"/personality/:path*",
"/tools/:path*",
"/runs/:path*",
"/archive/:path*",
"/admin/:path*",
],
};
/** 公开路径白名单 — 无需登录即可访问 */
const PUBLIC_PATHS = new Set([
"/api/auth/login",
"/api/auth/logout",
"/login",
"/register",
]);
/**
* 判断请求是否为页面导航(Accept: text/html)。
* API 请求(fetch/EventSource/XMLHttpRequest)返回 JSON 即可,不重定向。
*/
function isPageNavigation(req: NextRequest): boolean {
const accept = req.headers.get("accept") ?? "";
return accept.includes("text/html");
}
export function middleware(req: NextRequest) {
// 公开路径放行
if (PUBLIC_PATHS.has(req.nextUrl.pathname)) {
return NextResponse.next();
}
// 检查 hunjing-access cookie
const accessToken = req.cookies.get("hunjing-access")?.value;
if (!accessToken) {
// API 请求 → 返回 401 JSON
if (!isPageNavigation(req)) {
return NextResponse.json(
{ error: { code: "unauthorized", message: "未登录,请先登录。" } },
{ status: 401 },
);
}
// 页面导航 → 重定向到 /login
const loginUrl = new URL("/login", req.url);
loginUrl.searchParams.set("redirect", req.nextUrl.pathname);
return NextResponse.redirect(loginUrl);
}
return NextResponse.next();
}