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
This commit is contained in:
@@ -1,14 +1,53 @@
|
|||||||
/**
|
/**
|
||||||
* NextAuth App Router 入口(v4 模式)
|
* NextAuth v4 配置(route 端)
|
||||||
*
|
*
|
||||||
* v4 用单个 handler 函数,需手动 re-export 为 `GET` / `POST`:
|
* 极简模式:参考 `auth.js` 官方示例的**精神**(不做任何持久化),但保留 v4 语法:
|
||||||
* /api/auth/signin/[provider]
|
* - 客户端点击登录按钮 → `AuthPlatform.googleSignIn()` / `AuthPlatform.facebookSignIn()`
|
||||||
* /api/auth/callback/[provider]
|
* - NextAuth 重定向到 Google / Facebook OAuth
|
||||||
* /api/auth/signout
|
* - OAuth 回调 → NextAuth 写 `next-auth.session-token` (httpOnly) cookie
|
||||||
* /api/auth/session
|
* - 重定向到 callbackUrl (默认 /chat)
|
||||||
* /api/auth/csrf
|
* - 业务 token 持久化(unstorage / 后端 / 自定义 cookie)**全部不在本轮 scope**
|
||||||
* /api/auth/providers
|
*
|
||||||
|
* 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 };
|
||||||
|
|||||||
+1
-1
@@ -40,7 +40,7 @@ export default function RootLayout({
|
|||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
// suppressHydrationWarning:next/font + Tailwind v4 偶发类名差异;详见
|
// suppressHydrationWarning:next/font + Tailwind v4 偶发类名差异;详见
|
||||||
// `src/lib/auth/nextauth.ts` 的"水合闪屏"说明。
|
// `src/app/api/auth/[...nextauth]/route.ts` 的"水合闪屏"说明。
|
||||||
<html
|
<html
|
||||||
lang="en"
|
lang="en"
|
||||||
suppressHydrationWarning
|
suppressHydrationWarning
|
||||||
|
|||||||
@@ -2,12 +2,9 @@
|
|||||||
/**
|
/**
|
||||||
* AuthPlatform 统一 OAuth 登录入口
|
* AuthPlatform 统一 OAuth 登录入口
|
||||||
*
|
*
|
||||||
* 合并自原 FacebookLogin / GoogleLogin 两个类。
|
* **两个**独立方法(**不**再依赖构造器):
|
||||||
* 构造时传入 provider 名,调用 signIn() 触发 NextAuth OAuth 流程。
|
* - `googleSignIn()` → 触发 Google OAuth
|
||||||
*
|
* - `facebookSignIn()` → 触发 Facebook OAuth
|
||||||
* 用法:
|
|
||||||
* await new AuthPlatform("facebook").signIn()
|
|
||||||
* await new AuthPlatform("google").signIn()
|
|
||||||
*
|
*
|
||||||
* NextAuth 流程:
|
* NextAuth 流程:
|
||||||
* 1. NextAuth 重定向到 provider OAuth 授权页
|
* 1. NextAuth 重定向到 provider OAuth 授权页
|
||||||
@@ -16,16 +13,20 @@
|
|||||||
* 4. 重定向到 callbackUrl (默认 /chat)
|
* 4. 重定向到 callbackUrl (默认 /chat)
|
||||||
*
|
*
|
||||||
* **不**做任何持久化(不写 unstorage / 不调后端 / 不设自定义 cookie)。
|
* **不**做任何持久化(不写 unstorage / 不调后端 / 不设自定义 cookie)。
|
||||||
* 原始 Dart: nextauth-helpers.{facebookLogin, googleLogin}()
|
* 原始 Dart: nextauth-helpers.{googleLogin, facebookLogin}()
|
||||||
*/
|
*/
|
||||||
import { signIn } from "next-auth/react";
|
import { signIn } from "next-auth/react";
|
||||||
|
|
||||||
export type AuthProvider = "facebook" | "google";
|
export type AuthProvider = "facebook" | "google";
|
||||||
|
|
||||||
export class AuthPlatform {
|
export class AuthPlatform {
|
||||||
constructor(private readonly provider: AuthProvider) {}
|
/** 触发 Google OAuth 跳转 */
|
||||||
|
static async googleSignIn(): Promise<void> {
|
||||||
|
await signIn("google");
|
||||||
|
}
|
||||||
|
|
||||||
async signIn(): Promise<void> {
|
/** 触发 Facebook OAuth 跳转 */
|
||||||
await signIn(this.provider);
|
static async facebookSignIn(): Promise<void> {
|
||||||
|
await signIn("facebook");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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";
|
|
||||||
@@ -9,9 +9,8 @@
|
|||||||
* `next-auth/react.signIn`(OAuth 跳转)。
|
* `next-auth/react.signIn`(OAuth 跳转)。
|
||||||
*/
|
*/
|
||||||
import { setup, fromPromise, assign } from "xstate";
|
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 type { LoginStatus } from "@/models/auth/login-status";
|
||||||
import { authRepository } from "@/data/repositories/auth_repository";
|
import { authRepository } from "@/data/repositories/auth_repository";
|
||||||
import type { IAuthRepository } from "@/data/repositories/interfaces";
|
import type { IAuthRepository } from "@/data/repositories/interfaces";
|
||||||
@@ -72,11 +71,15 @@ const emailRegisterThenLoginActor = fromPromise<
|
|||||||
return "email" as LoginStatus;
|
return "email" as LoginStatus;
|
||||||
});
|
});
|
||||||
|
|
||||||
// OAuth 登录 actor:调 next-auth/react 的 signIn(provider) 触发 OAuth 跳转。
|
// OAuth 登录 actor:调 AuthPlatform 触发 OAuth 跳转。
|
||||||
// 成功路径下 NextAuth 会重定向离开本应用,actor 通常不会 resolve;
|
// 成功路径下 NextAuth 会重定向离开本应用,actor 通常不会 resolve;
|
||||||
// onDone 仍保留以处理 SDK 同步返回(极少见)。
|
// onDone 仍保留以处理 SDK 同步返回(极少见)。
|
||||||
const oauthSignInActor = fromPromise<void, AuthProvider>(async ({ input }) => {
|
const oauthSignInActor = fromPromise<void, AuthProvider>(async ({ input }) => {
|
||||||
await signIn(input);
|
if (input === "google") {
|
||||||
|
await AuthPlatform.googleSignIn();
|
||||||
|
} else {
|
||||||
|
await AuthPlatform.facebookSignIn();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// ========== OAuth token → 后端业务 token sync actors ==========
|
// ========== OAuth token → 后端业务 token sync actors ==========
|
||||||
|
|||||||
Vendored
+1
-1
@@ -3,7 +3,7 @@ import "next-auth";
|
|||||||
/**
|
/**
|
||||||
* 扩展 next-auth v4 的 Session 类型
|
* 扩展 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")
|
* - OAuth provider 名("google" | "facebook")
|
||||||
* - Google id_token
|
* - Google id_token
|
||||||
* - Facebook access_token
|
* - Facebook access_token
|
||||||
|
|||||||
Reference in New Issue
Block a user