refactor(auth): centralize auth routing via proxy and marker cookie

This commit is contained in:
2026-06-10 12:47:47 +08:00
parent 1db3e3ae31
commit 2066934094
7 changed files with 156 additions and 38 deletions
+28 -19
View File
@@ -1,16 +1,18 @@
/**
* Next.js 16 Proxy(原 `middleware.ts`
*
* 行为:
* - 当请求携带 NextAuth `next-auth.session-token` cookie 且命中 `/splash` 或 `/auth`
* 时,308 重定向到 `/chat`
* - 其他情况透传(`NextResponse.next()`)。
* 行为(鉴权路由统一在此处理,业务层组件不再做 auth-driven 跳转)
* - 已登录(`cozsweet_authed` cookie 存在)访问 `/splash` 或 `/auth`
* 308 重定向到 `/chat`
* - 未登录(cookie 缺失)访问 `/chat` 或 `/sidebar`
* → 308 重定向到 `/splash`(首界面)
* - 其他情况透传(`NextResponse.next()`
*
* 重要约束:
* - **不**对 `/chat` 做反向重定向:游客态是合法用户。
* - NextAuth 的 session cookie 由 `src/lib/auth/config.ts` 集中管理;本 proxy 仅做
* CDN 友好的"乐观"快路径
* - 真正的鉴权门在 Client 侧 `useAuthGate()`(用 `useSession()`
* - **不**检查 NextAuth 自带的 `next-auth.session-token`:统一用自定义
* `cozsweet_authed` 信号,社交 / 邮箱登录出口都同步设/清这个 cookie。
* - 真正的鉴权门在 Client 侧 `useAuthGate()`(用 `useSession()`),用于 UI 状态
* - 路由跳转(**唯一**鉴权跳转点)由本 proxy 集中处理
*
* Next 16 重要变更(参见 `node_modules/next/dist/docs/01-app/03-api-reference/03-file-conventions/proxy.md`):
* - 文件从 `middleware.ts` 重命名为 `proxy.ts``middleware` 仍可用但已 deprecated。
@@ -25,36 +27,43 @@ import type { NextRequest } from "next/server";
import {
AUTH_ONLY_ROUTES,
PROTECTED_ROUTES,
ROUTES,
type StaticRoute,
} from "@/router/routes";
const SESSION_COOKIE_NAMES = [
"next-auth.session-token",
"__Secure-next-auth.session-token",
];
/** 路由信号 cookie 名(由 `src/app/api/auth/marker/route.ts` 写) */
const AUTH_COOKIE = "cozsweet_authed";
function isAuthOnlyRoute(pathname: string): pathname is StaticRoute {
function isAuthOnlyRoute(pathname: string): boolean {
return (AUTH_ONLY_ROUTES as readonly string[]).includes(pathname);
}
function isProtectedRoute(pathname: string): boolean {
return (PROTECTED_ROUTES as readonly string[]).includes(pathname);
}
/**
* Proxy 入口
*/
export function proxy(request: NextRequest) {
const { pathname } = request.nextUrl;
// 检查 NextAuth session cookie 存在性(cookie value 存在即认为已登录)
const hasSession = SESSION_COOKIE_NAMES.some(
(name) => Boolean(request.cookies.get(name)?.value),
);
const hasAuth = Boolean(request.cookies.get(AUTH_COOKIE)?.value);
if (hasSession && isAuthOnlyRoute(pathname)) {
// 已登录访问未登录专属页 → /chat
if (hasAuth && isAuthOnlyRoute(pathname)) {
const url = request.nextUrl.clone();
url.pathname = ROUTES.chat;
return NextResponse.redirect(url, 308);
}
// 未登录访问登录态专属页 → /splash(首界面)
if (!hasAuth && isProtectedRoute(pathname)) {
const url = request.nextUrl.clone();
url.pathname = ROUTES.splash;
return NextResponse.redirect(url, 308);
}
return NextResponse.next();
}
+6
View File
@@ -33,6 +33,12 @@ export const AUTH_ONLY_ROUTES: readonly StaticRoute[] = [
ROUTES.auth,
] as const;
/** 仅登录态可见的路由(命中后未登录用户应跳走) */
export const PROTECTED_ROUTES: readonly StaticRoute[] = [
ROUTES.chat,
ROUTES.sidebar,
] as const;
/** 公开路由(游客态也允许访问) */
export const PUBLIC_ROUTES: readonly StaticRoute[] = [
ROUTES.chat,