From dbc9a0cd13a628d597500d23ad1432cc4793c508 Mon Sep 17 00:00:00 2001 From: chenhang Date: Fri, 12 Jun 2026 17:20:56 +0800 Subject: [PATCH] refactor(auth): inline NextAuth v4 config and simplify AuthPlatform API - Inline NextAuth v4 handler directly in `[...nextauth]/route.ts`, removing the `@/lib/auth/nextauth` abstraction layer - Add Google/Facebook providers with JWT/session callbacks that expose `idToken` and `accessToken` on the session for backend exchange - Refactor `AuthPlatform` from constructor-based to static methods (`googleSignIn()` / `facebookSignIn()`) - Update `auth-machine.ts` and layout comment reference to match new structure - Align with original Dart `nextauth-helpers.{googleLogin, facebookLogin}()` naming and keep persistence concerns (unstorage, backend, custom cookies) out of scope for this iteration --- src/app/api/auth/[...nextauth]/route.ts | 59 ++++++++++++++++++---- src/app/layout.tsx | 2 +- src/lib/auth/auth_platform.ts | 21 ++++---- src/lib/auth/nextauth.ts | 67 ------------------------- src/stores/auth/auth-machine.ts | 11 ++-- src/types/next-auth.d.ts | 2 +- 6 files changed, 69 insertions(+), 93 deletions(-) delete mode 100644 src/lib/auth/nextauth.ts diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts index cf46fa15..dffed736 100644 --- a/src/app/api/auth/[...nextauth]/route.ts +++ b/src/app/api/auth/[...nextauth]/route.ts @@ -1,14 +1,53 @@ /** - * NextAuth App Router 入口(v4 模式) + * NextAuth v4 配置(route 端) * - * v4 用单个 handler 函数,需手动 re-export 为 `GET` / `POST`: - * /api/auth/signin/[provider] - * /api/auth/callback/[provider] - * /api/auth/signout - * /api/auth/session - * /api/auth/csrf - * /api/auth/providers + * 极简模式:参考 `auth.js` 官方示例的**精神**(不做任何持久化),但保留 v4 语法: + * - 客户端点击登录按钮 → `AuthPlatform.googleSignIn()` / `AuthPlatform.facebookSignIn()` + * - NextAuth 重定向到 Google / Facebook OAuth + * - OAuth 回调 → NextAuth 写 `next-auth.session-token` (httpOnly) cookie + * - 重定向到 callbackUrl (默认 /chat) + * - 业务 token 持久化(unstorage / 后端 / 自定义 cookie)**全部不在本轮 scope** + * + * v4 模式:`NextAuth(options)` 返回单个 handler 函数(v5 才返回 `{ handlers, signIn, signOut, auth }`)。 + * 通过 `export { handler as GET, handler as POST }` 暴露给 Next.js 路由。 */ -import { GET, POST } from "@/lib/auth/nextauth"; +import NextAuth from "next-auth"; +import Google from "next-auth/providers/google"; +import Facebook from "next-auth/providers/facebook"; -export { GET, POST }; +const handler = NextAuth({ + providers: [ + Google({ + clientId: process.env.AUTH_GOOGLE_ID ?? "", + clientSecret: process.env.AUTH_GOOGLE_SECRET ?? "", + }), + Facebook({ + clientId: process.env.AUTH_FACEBOOK_ID ?? "", + clientSecret: process.env.AUTH_FACEBOOK_SECRET ?? "", + }), + ], + // 把 OAuth provider 的 token 塞进 NextAuth session,供前端取用: + // - Google: account.id_token → 调后端 /api/auth/google-login 换业务 token + // - Facebook: account.access_token → 调后端 /api/auth/facebook-login + // 仅在**首次 sign-in** 时把 `account` 注入到 JWT;后续请求 account=undefined + // (由 NextAuth 控制),所以下面的 `if (account)` 守卫是必要的。 + callbacks: { + async jwt({ token, account }) { + if (account) { + token.provider = account.provider; + token.idToken = account.id_token; + token.accessToken = account.access_token; + } + return token; + }, + async session({ session, token }) { + // 暴露给 `useSession()` —— 仅 client-side 派发 sync 事件时使用,不落 localStorage + session.provider = (token.provider as "google" | "facebook" | undefined) ?? undefined; + session.idToken = (token.idToken as string | undefined) ?? undefined; + session.accessToken = (token.accessToken as string | undefined) ?? undefined; + return session; + }, + }, +}); + +export { handler as GET, handler as POST }; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index fdabae16..2478bcce 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -40,7 +40,7 @@ export default function RootLayout({ }>) { return ( // suppressHydrationWarning:next/font + Tailwind v4 偶发类名差异;详见 - // `src/lib/auth/nextauth.ts` 的"水合闪屏"说明。 + // `src/app/api/auth/[...nextauth]/route.ts` 的"水合闪屏"说明。 { + await signIn("google"); + } - async signIn(): Promise { - await signIn(this.provider); + /** 触发 Facebook OAuth 跳转 */ + static async facebookSignIn(): Promise { + await signIn("facebook"); } } diff --git a/src/lib/auth/nextauth.ts b/src/lib/auth/nextauth.ts deleted file mode 100644 index d6c62f68..00000000 --- a/src/lib/auth/nextauth.ts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * NextAuth v4 配置(单一来源) - * - * 极简模式:参考 `auth.js` 官方示例的**精神**(不做任何持久化),但保留 v4 语法: - * - 客户端点击登录按钮 → `new AuthPlatform("google").signIn()` / `new AuthPlatform("facebook").signIn()` - * - NextAuth 重定向到 Google / Facebook OAuth - * - OAuth 回调 → NextAuth 写 `next-auth.session-token` (httpOnly) cookie - * - 重定向到 callbackUrl (默认 /chat) - * - 业务 token 持久化(unstorage / 后端 / 自定义 cookie)**全部不在本轮 scope** - * - * 导出 `GET` / `POST` 给 `src/app/api/auth/[...nextauth]/route.ts` 引用。 - * 客户端通过 `src/lib/auth/{auth_platform,logout}.ts` 触发。 - */ -import NextAuth from "next-auth"; -import Google from "next-auth/providers/google"; -import Facebook from "next-auth/providers/facebook"; - -/** - * v4 模式:`NextAuth(options)` 返回单个 handler 函数(v5 才返回 `{ handlers, signIn, signOut, auth }`)。 - * 通过 `export { handler as GET, handler as POST }` 暴露给 Next.js 路由。 - */ -const handler = NextAuth({ - providers: [ - Google({ - clientId: process.env.AUTH_GOOGLE_ID ?? "", - clientSecret: process.env.AUTH_GOOGLE_SECRET ?? "", - }), - Facebook({ - clientId: process.env.AUTH_FACEBOOK_ID ?? "", - clientSecret: process.env.AUTH_FACEBOOK_SECRET ?? "", - }), - ], - // 把 OAuth provider 的 token 塞进 NextAuth session,供前端取用: - // - Google: account.id_token → 调后端 /api/auth/google-login 换业务 token - // - Facebook: account.access_token → 调后端 /api/auth/facebook-login - // 仅在**首次 sign-in** 时把 `account` 注入到 JWT;后续请求 account=undefined - // (由 NextAuth 控制),所以下面的 `if (account)` 守卫是必要的。 - callbacks: { - async jwt({ token, account }) { - if (account) { - token.provider = account.provider; - token.idToken = account.id_token; - token.accessToken = account.access_token; - } - return token; - }, - async session({ session, token }) { - // 暴露给 `useSession()` —— 仅 client-side 派发 sync 事件时使用,不落 localStorage - session.provider = (token.provider as "google" | "facebook" | undefined) ?? undefined; - session.idToken = (token.idToken as string | undefined) ?? undefined; - session.accessToken = (token.accessToken as string | undefined) ?? undefined; - return session; - }, - }, -}); - -export { handler as GET, handler as POST }; - -// ============================================================ -// 客户端登录类(class-oriented 封装) -// 每个 class 一个独立文件,详见同目录的子模块。 -// 这里 re-export 仅为消费者提供 `@/lib/auth/nextauth` 单点导入。 -// 注:上方 `next-auth` 是 server-only,本文件编译时会被 Next.js 切分到 server bundle; -// 客户端 import `AuthPlatform` / `Logout` 时不会拉入。 -// ============================================================ -export { AuthPlatform, type AuthProvider } from "./auth_platform"; -export { Logout } from "./logout"; diff --git a/src/stores/auth/auth-machine.ts b/src/stores/auth/auth-machine.ts index 1e3f62bf..efc2ce37 100644 --- a/src/stores/auth/auth-machine.ts +++ b/src/stores/auth/auth-machine.ts @@ -9,9 +9,8 @@ * `next-auth/react.signIn`(OAuth 跳转)。 */ import { setup, fromPromise, assign } from "xstate"; -import { signIn } from "next-auth/react"; -import type { AuthProvider } from "@/lib/auth/auth_platform"; +import { AuthPlatform, type AuthProvider } from "@/lib/auth/auth_platform"; import type { LoginStatus } from "@/models/auth/login-status"; import { authRepository } from "@/data/repositories/auth_repository"; import type { IAuthRepository } from "@/data/repositories/interfaces"; @@ -72,11 +71,15 @@ const emailRegisterThenLoginActor = fromPromise< return "email" as LoginStatus; }); -// OAuth 登录 actor:调 next-auth/react 的 signIn(provider) 触发 OAuth 跳转。 +// OAuth 登录 actor:调 AuthPlatform 触发 OAuth 跳转。 // 成功路径下 NextAuth 会重定向离开本应用,actor 通常不会 resolve; // onDone 仍保留以处理 SDK 同步返回(极少见)。 const oauthSignInActor = fromPromise(async ({ input }) => { - await signIn(input); + if (input === "google") { + await AuthPlatform.googleSignIn(); + } else { + await AuthPlatform.facebookSignIn(); + } }); // ========== OAuth token → 后端业务 token sync actors ========== diff --git a/src/types/next-auth.d.ts b/src/types/next-auth.d.ts index 549e0c26..c7556c84 100644 --- a/src/types/next-auth.d.ts +++ b/src/types/next-auth.d.ts @@ -3,7 +3,7 @@ import "next-auth"; /** * 扩展 next-auth v4 的 Session 类型 * - * `src/lib/auth/nextauth.ts` 的 jwt/session callbacks 把 + * `src/app/api/auth/[...nextauth]/route.ts` 的 jwt/session callbacks 把 * - OAuth provider 名("google" | "facebook") * - Google id_token * - Facebook access_token