refactor(auth): centralize auth routing via proxy and marker cookie
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* 路由信号 cookie 管理端点
|
||||
*
|
||||
* 用途:写/清 `cozsweet_authed` httpOnly cookie,作为 `src/router/proxy.ts`
|
||||
* 的"用户已登录"信号源。
|
||||
*
|
||||
* 与 `next-auth.session-token` 的区别:
|
||||
* - NextAuth cookie 仅社交登录设置,邮箱登录不写入
|
||||
* - 本端点由所有登录出口(社交 / 邮箱)统一调用,proxy 只需读这一个 cookie
|
||||
*
|
||||
* 调用方:
|
||||
* - 社交登录出口(nextauth-helpers.persistBackendSession,**server 端**直接用
|
||||
* `next/headers.cookies()` 写,不走本端点)
|
||||
* - 邮箱登录出口(auth-machine.emailLoginActor / emailRegisterThenLoginActor,
|
||||
* **client 端** fetch POST)
|
||||
* - 登出(nextauth-helpers.logout,client 端 fetch DELETE)
|
||||
*
|
||||
* 安全:cookie 设为 `HttpOnly` + `SameSite=Lax`,prod 额外 `Secure`。
|
||||
*/
|
||||
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
const COOKIE_NAME = "cozsweet_authed";
|
||||
const ONE_WEEK = 60 * 60 * 24 * 7;
|
||||
|
||||
/** 设置 httpOnly 路由信号 cookie(任何登录成功后调用) */
|
||||
export async function POST() {
|
||||
const res = NextResponse.json({ ok: true });
|
||||
res.cookies.set({
|
||||
name: COOKIE_NAME,
|
||||
value: "1",
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
sameSite: "lax",
|
||||
path: "/",
|
||||
maxAge: ONE_WEEK,
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
/** 清除路由信号 cookie(登出时调用) */
|
||||
export async function DELETE() {
|
||||
const res = NextResponse.json({ ok: true });
|
||||
res.cookies.set({
|
||||
name: COOKIE_NAME,
|
||||
value: "",
|
||||
path: "/",
|
||||
maxAge: 0,
|
||||
});
|
||||
return res;
|
||||
}
|
||||
@@ -13,10 +13,9 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
import { useAuthState, useAuthDispatch } from "@/stores/auth/auth-context";
|
||||
import { useAuthState } from "@/stores/auth/auth-context";
|
||||
import { useChatDispatch } from "@/stores/chat/chat-context";
|
||||
import { useUserDispatch } from "@/stores/user/user-context";
|
||||
import { useAuthGate } from "@/lib/auth/nextauth";
|
||||
import { ROUTES } from "@/router/routes";
|
||||
import { MobileShell } from "@/app/_components/core/mobile-shell";
|
||||
import { AuthPanel } from "@/app/auth/components/auth-panel";
|
||||
@@ -29,19 +28,17 @@ export interface AuthScreenProps {
|
||||
export function AuthScreen({
|
||||
background = "var(--color-sidebar-background)",
|
||||
}: AuthScreenProps) {
|
||||
// ===== 鉴权路由跳转已由 src/router/proxy.ts 集中处理 =====
|
||||
// 本组件只负责:
|
||||
// 1. 渲染登录面板
|
||||
// 2. 登录成功后初始化 chat/user store + navigate 到 /chat(业务层动作)
|
||||
|
||||
const router = useRouter();
|
||||
const { isAuthed } = useAuthGate();
|
||||
const state = useAuthState();
|
||||
const authDispatch = useAuthDispatch();
|
||||
const chatDispatch = useChatDispatch();
|
||||
const userDispatch = useUserDispatch();
|
||||
const wasSuccess = useRef(false);
|
||||
|
||||
// 已登录 → 自动跳 /chat
|
||||
useEffect(() => {
|
||||
if (isAuthed) router.replace(ROUTES.chat);
|
||||
}, [isAuthed, router]);
|
||||
|
||||
// 邮箱登录成功 → 通知 Chat + User + 跳转
|
||||
useEffect(() => {
|
||||
if (state.isSuccess && !wasSuccess.current) {
|
||||
|
||||
@@ -17,10 +17,10 @@ import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
import { useAuthState, useAuthDispatch } from "@/stores/auth/auth-context";
|
||||
import { useAuthState } from "@/stores/auth/auth-context";
|
||||
import { useChatDispatch } from "@/stores/chat/chat-context";
|
||||
import { useUserDispatch } from "@/stores/user/user-context";
|
||||
import { facebookLogin, useAuthGate } from "@/lib/auth/nextauth";
|
||||
import { facebookLogin } from "@/lib/auth/nextauth";
|
||||
import { ROUTES } from "@/router/routes";
|
||||
import { LoadingIndicator } from "@/app/_components/core/loading-indicator";
|
||||
import styles from "./splash-button.module.css";
|
||||
@@ -31,10 +31,13 @@ export interface SplashButtonProps {
|
||||
}
|
||||
|
||||
export function SplashButton({ skipHref = ROUTES.chat }: SplashButtonProps) {
|
||||
// ===== 鉴权状态 =====
|
||||
const { isAuthed } = useAuthGate();
|
||||
// ===== 鉴权路由跳转已由 src/router/proxy.ts 集中处理 =====
|
||||
// 本组件只负责:
|
||||
// 1. 触发登录动作
|
||||
// 2. 登录成功后初始化 chat/user store + navigate 到 /chat(业务层动作,
|
||||
// 不是鉴权判断;proxy 会让通过因为 cookie 已设)
|
||||
|
||||
const state = useAuthState();
|
||||
const authDispatch = useAuthDispatch();
|
||||
const isLoading = state.isLoading;
|
||||
|
||||
// ===== 跨 store 初始化(登录成功后触发) =====
|
||||
@@ -43,11 +46,6 @@ export function SplashButton({ skipHref = ROUTES.chat }: SplashButtonProps) {
|
||||
const router = useRouter();
|
||||
const wasSuccess = useRef(false);
|
||||
|
||||
// 已登录时跳 /chat
|
||||
useEffect(() => {
|
||||
if (isAuthed) router.replace(ROUTES.chat);
|
||||
}, [isAuthed, router]);
|
||||
|
||||
// 邮箱登录成功跳 /chat + 通知 chat/user 初始化
|
||||
useEffect(() => {
|
||||
if (state.isSuccess && !wasSuccess.current) {
|
||||
|
||||
Reference in New Issue
Block a user