From 9772ba88ae9545a66c026b49dd376f4420c1e1d0 Mon Sep 17 00:00:00 2001 From: iye <1713042409@qq.com> Date: Mon, 18 May 2026 17:05:50 +0800 Subject: [PATCH] =?UTF-8?q?fix(auth):=20=E5=83=B5=E5=B0=B8=20JWT=20session?= =?UTF-8?q?=20=E5=85=9C=E5=BA=95=20=E2=80=94=E2=80=94=20/api/me=20?= =?UTF-8?q?=E8=BF=94=E5=9B=9E=20NOT=5FFOUND/UNAUTHORIZED=20=E6=97=B6?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E7=99=BB=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NextAuth 用 JWT 策略,cookie 签名不会因 DB user 被删而失效, 导致 dev 清数据后浏览器仍显示"已登录"但拉不到任何数据(假登录)。 useSyncMe 现在识别 /api/me 的 401/NOT_FOUND/UNAUTHORIZED 三种信号, 命中后调用 signOut({ redirect: false }) + reset(),把 UI 切回未登录态。 生产环境不会清 user,主要受益是 dev/staging 重置数据后无需手动清浏览器 cookie。 Co-Authored-By: Claude Opus 4.7 --- src/hooks/useSyncMe.ts | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/hooks/useSyncMe.ts b/src/hooks/useSyncMe.ts index 5a15bbd..fa130fc 100644 --- a/src/hooks/useSyncMe.ts +++ b/src/hooks/useSyncMe.ts @@ -1,7 +1,7 @@ "use client"; import { useEffect, useRef } from "react"; -import { useSession } from "next-auth/react"; +import { useSession, signOut } from "next-auth/react"; import { useVoteStore } from "@/lib/store"; /** @@ -11,6 +11,10 @@ import { useVoteStore } from "@/lib/store"; * - status === "unauthenticated" → 清本地(避免上一个用户的票残留给下一个登录者) * - 切换用户(uid 变化) → 重新拉一次 * + * 僵尸 session 兜底:NextAuth 用 JWT 策略,cookie 不会因 DB user 被删而失效。 + * 当 /api/me 返回 401(签名失效) 或 NOT_FOUND(DB 里 user 已不存在) 时, + * 自动 signOut() 清 cookie —— 避免页面"假登录"假象(显示已登录但拉不到数据)。 + * * localStorage 仅作为本设备的缓存加速首屏渲染,服务端永远是唯一真相源。 */ export function useSyncMe() { @@ -45,12 +49,23 @@ export function useSyncMe() { credentials: "include", signal: ctrl.signal, }) - .then((r) => r.json()) - .then((res) => { + .then(async (r) => { if (cancelled) return; - if (res?.ok && Array.isArray(res.data?.votedArtists)) { + const res = await r.json().catch(() => null); + + if (r.ok && res?.ok && Array.isArray(res.data?.votedArtists)) { hydrateFromServer(res.data.votedArtists as string[]); lastSyncedUidRef.current = uid; + return; + } + + // 僵尸 session:JWT 还有效,但 DB 里 user 已不存在(或鉴权失效)。 + // 直接登出清 cookie,UI 状态切换为未登录,避免"显示已登录但拉不到数据"。 + const code: string | undefined = res?.error?.code; + if (r.status === 401 || code === "UNAUTHORIZED" || code === "NOT_FOUND") { + signOut({ redirect: false }); + reset(); + lastSyncedUidRef.current = null; } }) .catch(() => {