fix(auth): 僵尸 JWT session 兜底 —— /api/me 返回 NOT_FOUND/UNAUTHORIZED 时自动登出
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 3m54s

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 <noreply@anthropic.com>
This commit is contained in:
iye 2026-05-18 17:05:50 +08:00
parent 8b99c2f091
commit 9772ba88ae

View File

@ -1,7 +1,7 @@
"use client"; "use client";
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import { useSession } from "next-auth/react"; import { useSession, signOut } from "next-auth/react";
import { useVoteStore } from "@/lib/store"; import { useVoteStore } from "@/lib/store";
/** /**
@ -11,6 +11,10 @@ import { useVoteStore } from "@/lib/store";
* - status === "unauthenticated" () * - status === "unauthenticated" ()
* - (uid ) * - (uid )
* *
* session 兜底:NextAuth JWT ,cookie DB user
* /api/me 401() NOT_FOUND(DB user ) ,
* signOut() cookie "假登录"()
*
* localStorage , * localStorage ,
*/ */
export function useSyncMe() { export function useSyncMe() {
@ -45,12 +49,23 @@ export function useSyncMe() {
credentials: "include", credentials: "include",
signal: ctrl.signal, signal: ctrl.signal,
}) })
.then((r) => r.json()) .then(async (r) => {
.then((res) => {
if (cancelled) return; 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[]); hydrateFromServer(res.data.votedArtists as string[]);
lastSyncedUidRef.current = uid; 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(() => { .catch(() => {