From 3f3523f1bbebd83c681ed9f58aea2ae0fce3bd73 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 14 Feb 2026 00:39:46 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E7=89=88=E9=A6=96=E9=A1=B5=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=BC=B9=E7=AA=97=E7=99=BB=E5=BD=95=E5=B9=B6=E6=8E=A5?= =?UTF-8?q?=E5=85=A5Google=E5=90=8E=E5=8F=B0=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../settings/admin-settings-client.tsx | 77 ++++++ app/(auth)/auth.config.ts | 87 ++++--- app/(auth)/auth.ts | 152 ++++++++--- app/(auth)/login/page.tsx | 66 +++-- app/(chat)/page.tsx | 26 +- app/api/auth-settings/route.ts | 37 +++ app/api/featured/route.ts | 7 - components/home/lovart-home.tsx | 235 +++++++++++++++++- .../0030_add_google_auth_toggle.sql | 2 + .../0031_add_google_oauth_settings.sql | 4 + lib/db/migrations/meta/_journal.json | 14 ++ lib/db/queries.ts | 8 + lib/db/schema.ts | 4 + proxy.ts | 14 +- 14 files changed, 616 insertions(+), 117 deletions(-) create mode 100644 lib/db/migrations/0030_add_google_auth_toggle.sql create mode 100644 lib/db/migrations/0031_add_google_oauth_settings.sql diff --git a/app/(admin)/settings/admin-settings-client.tsx b/app/(admin)/settings/admin-settings-client.tsx index 67ebea111..ec86d8a9e 100644 --- a/app/(admin)/settings/admin-settings-client.tsx +++ b/app/(admin)/settings/admin-settings-client.tsx @@ -217,7 +217,11 @@ type AuthSettings = { // 保留单一配置用于兼容旧数据(后端会自动同步为第一个组织) dingTalkAppKey?: string | null; dingTalkAppSecret?: string | null; + googleClientId?: string | null; + googleClientSecret?: string | null; + authUrl?: string | null; isDingTalkEnabled: boolean; + isGoogleEnabled: boolean; isRegistrationEnabled?: boolean; dingTalkOrganizations: DingTalkOrganization[]; }; @@ -776,10 +780,15 @@ export function AdminSettingsClient() { const [authSettings, setAuthSettings] = useState({ dingTalkAppKey: "", dingTalkAppSecret: "", + googleClientId: "", + googleClientSecret: "", + authUrl: "", isDingTalkEnabled: false, + isGoogleEnabled: false, isRegistrationEnabled: true, dingTalkOrganizations: [], }); + const [isGoogleConfigured, setIsGoogleConfigured] = useState(false); useEffect(() => { if (!authSettingsData) return; @@ -804,10 +813,15 @@ export function AdminSettingsClient() { setAuthSettings({ dingTalkAppKey: authSettingsData.dingTalkAppKey || "", dingTalkAppSecret: authSettingsData.dingTalkAppSecret || "", + googleClientId: authSettingsData.googleClientId || "", + googleClientSecret: authSettingsData.googleClientSecret || "", + authUrl: authSettingsData.authUrl || "", isDingTalkEnabled: authSettingsData.isDingTalkEnabled || false, + isGoogleEnabled: authSettingsData.isGoogleEnabled || false, isRegistrationEnabled: authSettingsData.isRegistrationEnabled ?? true, dingTalkOrganizations: normalizedOrgs, }); + setIsGoogleConfigured(Boolean(authSettingsData.isGoogleConfigured)); }, [authSettingsData]); // AI 提供商 & 模型状态 @@ -3863,6 +3877,69 @@ export function AdminSettingsClient() { +
+

Google 登录

+

+ 通过 Google OAuth 登录,可直接在这里配置 Google 凭据与 AUTH_URL。 +

+
+
+ + + setAuthSettings({ ...authSettings, googleClientId: e.target.value }) + } + placeholder="Google OAuth Client ID" + /> +
+
+ + + setAuthSettings({ ...authSettings, googleClientSecret: e.target.value }) + } + placeholder="Google OAuth Client Secret" + /> +
+
+ + + setAuthSettings({ ...authSettings, authUrl: e.target.value }) + } + placeholder="https://your-domain.com" + /> +
+
+
+
+ +

+ 当前服务器配置状态:{isGoogleConfigured ? "已配置" : "未配置"} +

+
+ + setAuthSettings({ ...authSettings, isGoogleEnabled: checked }) + } + /> +
+ {!isGoogleConfigured ? ( +

+ 未检测到完整 Google 配置(Client ID / Client Secret)。 +

+ ) : null} +
+ diff --git a/app/(auth)/auth.config.ts b/app/(auth)/auth.config.ts index e7de48b76..57a473511 100644 --- a/app/(auth)/auth.config.ts +++ b/app/(auth)/auth.config.ts @@ -1,49 +1,48 @@ import type { NextAuthConfig } from "next-auth"; -// 检测是否为 HTTPS 环境 -const isSecure = process.env.AUTH_URL?.startsWith("https://") ?? false; -const hostName = process.env.AUTH_URL ? new URL(process.env.AUTH_URL).hostname : undefined; -const cookieDomain = hostName && hostName !== "localhost" ? hostName : undefined; +export function buildAuthConfig(authUrl?: string | null): NextAuthConfig { + const resolvedAuthUrl = authUrl || process.env.AUTH_URL || ""; + const isSecure = resolvedAuthUrl.startsWith("https://"); + const hostName = resolvedAuthUrl ? new URL(resolvedAuthUrl).hostname : undefined; + const cookieDomain = hostName && hostName !== "localhost" ? hostName : undefined; -export const authConfig = { - trustHost: true, - pages: { - signIn: "/login", - newUser: "/", - }, - cookies: { - sessionToken: { - name: isSecure ? "__Secure-authjs.session-token" : "authjs.session-token", - options: { - httpOnly: true, - sameSite: "lax", - path: "/", - secure: isSecure, - domain: cookieDomain, + return { + trustHost: true, + pages: { + signIn: "/login", + newUser: "/", + }, + cookies: { + sessionToken: { + name: isSecure ? "__Secure-authjs.session-token" : "authjs.session-token", + options: { + httpOnly: true, + sameSite: "lax", + path: "/", + secure: isSecure, + domain: cookieDomain, + }, + }, + callbackUrl: { + name: isSecure ? "__Secure-authjs.callback-url" : "authjs.callback-url", + options: { + sameSite: "lax", + path: "/", + secure: isSecure, + domain: cookieDomain, + }, + }, + csrfToken: { + name: isSecure ? "__Host-authjs.csrf-token" : "authjs.csrf-token", + options: { + httpOnly: true, + sameSite: "lax", + path: "/", + secure: isSecure, + }, }, }, - callbackUrl: { - name: isSecure ? "__Secure-authjs.callback-url" : "authjs.callback-url", - options: { - sameSite: "lax", - path: "/", - secure: isSecure, - domain: cookieDomain, - }, - }, - csrfToken: { - name: isSecure ? "__Host-authjs.csrf-token" : "authjs.csrf-token", - options: { - httpOnly: true, - sameSite: "lax", - path: "/", - secure: isSecure, - }, - }, - }, - providers: [ - // added later in auth.ts since it requires bcrypt which is only compatible with Node.js - // while this file is also used in non-Node.js environments - ], - callbacks: {}, -} satisfies NextAuthConfig; + providers: [], + callbacks: {}, + }; +} diff --git a/app/(auth)/auth.ts b/app/(auth)/auth.ts index 52f40aa66..90db58565 100644 --- a/app/(auth)/auth.ts +++ b/app/(auth)/auth.ts @@ -2,11 +2,12 @@ import { compare } from "bcrypt-ts"; import NextAuth, { type DefaultSession } from "next-auth"; import type { DefaultJWT } from "next-auth/jwt"; import Credentials from "next-auth/providers/credentials"; +import Google from "next-auth/providers/google"; import { DUMMY_PASSWORD } from "@/lib/constants"; -import { createGuestUser, getUser } from "@/lib/db/queries"; -import { authConfig } from "./auth.config"; +import { createGuestUser, getAuthSettings, getUser } from "@/lib/db/queries"; +import { buildAuthConfig } from "./auth.config"; import { db } from "@/lib/db"; -import { loginToken } from "@/lib/db/schema"; +import { loginToken, user as userTable } from "@/lib/db/schema"; import { eq, and, gt } from "drizzle-orm"; export type UserType = "guest" | "regular" | "admin"; @@ -25,7 +26,7 @@ declare module "next-auth" { id?: string; email?: string | null; name?: string | null; - type: UserType; + type?: UserType; } } @@ -37,15 +38,28 @@ declare module "next-auth/jwt" { } } -export const { - handlers: { GET, POST }, - auth, - signIn, - signOut, -} = NextAuth({ - ...authConfig, - trustHost: true, - providers: [ +async function getOrCreateGoogleUser(email: string, name?: string | null) { + const existingUsers = await getUser(email); + if (existingUsers.length > 0) { + return existingUsers[0]; + } + + const [createdUser] = await db + .insert(userTable) + .values({ + email, + password: "", + name: (name || email.split("@")[0] || "").slice(0, 100), + role: "regular", + isActive: true, + }) + .returning(); + + return createdUser || null; +} + +function createBaseProviders(googleClientId?: string, googleClientSecret?: string) { + const providers: any[] = [ Credentials({ credentials: {}, async authorize(credentials: any) { @@ -141,25 +155,101 @@ export const { return { ...guestUser, type: "guest" }; }, }), - ], - callbacks: { - jwt({ token, user }) { - if (user) { - token.id = user.id as string; - token.type = user.type; - token.name = user.name; - } + ]; - return token; - }, - session({ session, token }) { - if (session.user) { - session.user.id = token.id; - session.user.type = token.type; - session.user.name = token.name as string; - } + if (googleClientId && googleClientSecret) { + providers.push( + Google({ + clientId: googleClientId, + clientSecret: googleClientSecret, + }) + ); + } - return session; + return providers; +} + +export const { + handlers: { GET, POST }, + auth, + signIn, + signOut, +} = NextAuth(async () => { + const runtimeAuthSettings = await getAuthSettings().catch(() => null as any); + const resolvedAuthUrl = + runtimeAuthSettings?.authUrl || process.env.AUTH_URL || undefined; + const googleClientId = + runtimeAuthSettings?.googleClientId || process.env.GOOGLE_CLIENT_ID || ""; + const googleClientSecret = + runtimeAuthSettings?.googleClientSecret || process.env.GOOGLE_CLIENT_SECRET || ""; + + return { + ...buildAuthConfig(resolvedAuthUrl), + trustHost: true, + providers: createBaseProviders(googleClientId, googleClientSecret), + callbacks: { + async signIn({ account, user, profile }) { + if (account?.provider !== "google") { + return true; + } + + const authSettings = await getAuthSettings(); + if (!authSettings.isGoogleEnabled) { + return false; + } + + const email = (user?.email || (profile as any)?.email || "").trim().toLowerCase(); + if (!email) { + return false; + } + + const dbUser = await getOrCreateGoogleUser( + email, + user?.name || (profile as any)?.name || null + ); + + return Boolean(dbUser?.isActive); + }, + async jwt({ token, user, account, profile }) { + if (user?.id && user?.type) { + token.id = user.id as string; + token.type = user.type; + token.name = user.name; + } + + if (account?.provider === "google") { + const email = String( + user?.email || (profile as any)?.email || token.email || "" + ) + .trim() + .toLowerCase(); + + if (email) { + const dbUser = await getOrCreateGoogleUser( + email, + user?.name || (profile as any)?.name || null + ); + + if (dbUser) { + token.id = dbUser.id; + token.type = (dbUser.role as UserType) || "regular"; + token.name = dbUser.name; + token.email = dbUser.email; + } + } + } + + return token; + }, + session({ session, token }) { + if (session.user) { + session.user.id = token.id; + session.user.type = token.type || "regular"; + session.user.name = token.name as string; + } + + return session; + }, }, - }, + }; }); diff --git a/app/(auth)/login/page.tsx b/app/(auth)/login/page.tsx index 9f154541e..453243523 100644 --- a/app/(auth)/login/page.tsx +++ b/app/(auth)/login/page.tsx @@ -28,6 +28,8 @@ export default function Page() { const [siteLogo, setSiteLogo] = useState(null); const [siteLogoDark, setSiteLogoDark] = useState(null); const [isDingTalkEnabled, setIsDingTalkEnabled] = useState(false); + const [isGoogleEnabled, setIsGoogleEnabled] = useState(false); + const [isGoogleConfigured, setIsGoogleConfigured] = useState(false); const [dingTalkAppKey, setDingTalkAppKey] = useState(null); const [dingTalkOrganizations, setDingTalkOrganizations] = useState([]); const [isRegistrationEnabled, setIsRegistrationEnabled] = useState(true); @@ -215,6 +217,8 @@ export default function Page() { .then((res) => res.json()) .then((data) => { if (data.isDingTalkEnabled) setIsDingTalkEnabled(true); + if (data.isGoogleEnabled) setIsGoogleEnabled(true); + if (data.isGoogleConfigured) setIsGoogleConfigured(true); if (data.dingTalkAppKey) setDingTalkAppKey(data.dingTalkAppKey); if (Array.isArray(data.organizations)) { setDingTalkOrganizations( @@ -230,6 +234,20 @@ export default function Page() { .catch(console.error); }, []); + const handleGoogleLogin = async () => { + if (!isGoogleEnabled) { + toast({ type: "error", description: "Google 登录未启用" }); + return; + } + + if (!isGoogleConfigured) { + toast({ type: "error", description: "Google 登录未完成服务器配置" }); + return; + } + + await signIn("google", { callbackUrl: "/" }); + }; + // 检查 URL 中是否有钉钉 Token(页面加载时自动登录) useEffect(() => { const params = new URLSearchParams(window.location.search); @@ -413,6 +431,9 @@ export default function Page() { const displayLogo = mounted ? (resolvedTheme === "dark" && siteLogoDark ? siteLogoDark : siteLogo) : siteLogo; + const hasThirdPartyLogin = + (isDingTalkEnabled && (Boolean(dingTalkAppKey) || dingTalkOrganizations.length > 0)) || + (isGoogleEnabled && isGoogleConfigured); return (
@@ -448,7 +469,7 @@ export default function Page() { 登录 - {isDingTalkEnabled && (dingTalkAppKey || dingTalkOrganizations.length > 0) && ( + {hasThirdPartyLogin && (
@@ -460,19 +481,36 @@ export default function Page() {
- +
+ {isDingTalkEnabled && (dingTalkAppKey || dingTalkOrganizations.length > 0) ? ( + + ) : null} + + {isGoogleEnabled && isGoogleConfigured ? ( + + ) : null} +
{/* 多组织下的小型选择框(悬浮在按钮附近) */} {dingTalkOrganizations.length > 1 && showOrgMenu && ( diff --git a/app/(chat)/page.tsx b/app/(chat)/page.tsx index 655b80328..e1635d2c6 100644 --- a/app/(chat)/page.tsx +++ b/app/(chat)/page.tsx @@ -34,11 +34,6 @@ async function NewChatPage() { getSystemSetting({ key: "home_surprise_categories" }).catch(() => undefined), ]); - // 如果用户未登录,重定向到登录页面 - if (!session?.user) { - redirect("/login"); - } - const homeVariant = homeVariantSetting?.value === "lovart" ? "lovart" : "classic"; const surpriseCategories = Array.isArray(surpriseCategoriesSetting?.value) ? surpriseCategoriesSetting?.value @@ -46,15 +41,24 @@ async function NewChatPage() { .filter(Boolean) : ["全部", "惊喜"]; + if (!session?.user && homeVariant !== "lovart") { + redirect("/login"); + } + if (homeVariant === "lovart") { return ( (null); @@ -202,7 +217,7 @@ export function LovartHome({ const { data: canvasData, mutate: mutateCanvas } = useSWR<{ success: boolean; data: CanvasProject[]; - }>("/api/canvas", fetcher); + }>(isAuthenticated ? "/api/canvas" : null, fetcher); const { data: featuredPages, size: featuredPageCount, setSize: setFeaturedPageCount, isValidating: isFeaturedValidating } = useSWRInfinite( @@ -224,19 +239,24 @@ export function LovartHome({ ); const { data: membershipData } = useSWR( - "/api/user/membership-summary", + isAuthenticated ? "/api/user/membership-summary" : null, fetcher ); const { data: modelsData } = useSWR<{ models: ModelOption[]; - }>("/api/models", fetcher); + }>(isAuthenticated ? "/api/models" : null, fetcher); const { data: siteSettingsData } = useSWR<{ canvasDefaultModelId?: string | null; lovartComposerPreviewTexts?: string[]; }>("/api/site-settings", fetcher); + const { data: authSettingsData } = useSWR( + "/api/auth-settings", + fetcher + ); + const membershipConfig = membershipData?.config; const membershipPlans = membershipConfig?.plans || []; const currentPlan = membershipData?.currentPlan; @@ -337,6 +357,12 @@ export function LovartHome({ return (canvasData?.data || []).length > 4; }, [canvasData?.data]); + const canUseGoogleLogin = Boolean( + authSettingsData?.isGoogleEnabled && authSettingsData?.isGoogleConfigured + ); + const canUseDingTalkLogin = Boolean(authSettingsData?.isDingTalkEnabled); + const hasThirdPartyLoginOptions = canUseGoogleLogin || canUseDingTalkLogin; + useEffect(() => { if (selectedModelId) return; const defaultModelId = siteSettingsData?.canvasDefaultModelId || "auto"; @@ -351,6 +377,18 @@ export function LovartHome({ setSelectedModelId("auto"); }, [allComposerModels, selectedModelId, modelsData]); + useEffect(() => { + if (!isAuthenticated) return; + setLoginDialogOpen(false); + }, [isAuthenticated]); + + useEffect(() => { + if (!loginDialogOpen) { + setIsLoggingIn(false); + setLoginPassword(""); + } + }, [loginDialogOpen]); + useEffect(() => { if (typeof window === "undefined") return; try { @@ -508,7 +546,56 @@ export function LovartHome({ return result; }; + const openLoginDialog = () => { + setLoginDialogOpen(true); + }; + + const handleEmailLogin = async () => { + const email = loginEmail.trim(); + if (!email || !loginPassword) { + toast.error("请输入邮箱和密码"); + return; + } + + setIsLoggingIn(true); + try { + const result = await signIn("credentials", { + email, + password: loginPassword, + redirect: false, + }); + + if (result?.error) { + toast.error("登录失败,请检查账号密码"); + return; + } + + window.location.href = "/"; + } catch (_error) { + toast.error("登录失败,请稍后重试"); + } finally { + setIsLoggingIn(false); + } + }; + + const handleGoogleLogin = async () => { + if (!canUseGoogleLogin) { + toast.error("Google 登录未启用"); + return; + } + await signIn("google", { callbackUrl: "/" }); + }; + + const handleDingTalkLogin = () => { + window.location.href = "/login"; + }; + const createCanvasFromComposer = async () => { + if (!isAuthenticated) { + openLoginDialog(); + return; + } + if (!prompt.trim() && files.length === 0) return; setIsSubmitting(true); @@ -562,10 +649,18 @@ export function LovartHome({ }; const openNewCanvas = () => { + if (!isAuthenticated) { + openLoginDialog(); + return; + } router.push("/canvas?new=1"); }; const openAllProjectsInPage = () => { + if (!isAuthenticated) { + openLoginDialog(); + return; + } setShowAllProjects(true); projectSectionRef.current?.scrollIntoView({ behavior: "smooth", block: "start" }); }; @@ -658,9 +753,19 @@ export function LovartHome({ - + {isAuthenticated ? ( + + ) : ( + + )}
@@ -705,7 +810,7 @@ export function LovartHome({ > - {user.type === "admin" ? ( + {user?.type === "admin" ? ( + {loginDialogOpen ? ( +
setLoginDialogOpen(false)} + > +
event.stopPropagation()} + > +
+
+ {siteLogo ? ( + {siteName} + ) : ( +
+ +
+ )} + + {siteName} + +
+ +
+ +
+

欢迎来到 {siteName}

+

登录或注册以继续

+
+ + {hasThirdPartyLoginOptions ? ( +
+ {canUseGoogleLogin ? ( + + ) : null} + {canUseDingTalkLogin ? ( + + ) : null} +
+
+ ) : null} + +
{ + event.preventDefault(); + handleEmailLogin(); + }} + > + setLoginEmail(event.target.value)} + placeholder="电子邮箱" + type="email" + className="h-14 rounded-xl border-black/20 px-4 text-base" + /> + setLoginPassword(event.target.value)} + placeholder="密码" + type="password" + className="h-14 rounded-xl border-black/20 px-4 text-base" + /> + +
+ +

+ 继续即表示您同意使用条款和隐私政策 +

+
+
+ ) : null} +
diff --git a/lib/db/migrations/0030_add_google_auth_toggle.sql b/lib/db/migrations/0030_add_google_auth_toggle.sql new file mode 100644 index 000000000..8f61b4940 --- /dev/null +++ b/lib/db/migrations/0030_add_google_auth_toggle.sql @@ -0,0 +1,2 @@ +ALTER TABLE "AuthSettings" +ADD COLUMN IF NOT EXISTS "isGoogleEnabled" boolean NOT NULL DEFAULT false; diff --git a/lib/db/migrations/0031_add_google_oauth_settings.sql b/lib/db/migrations/0031_add_google_oauth_settings.sql new file mode 100644 index 000000000..d75676341 --- /dev/null +++ b/lib/db/migrations/0031_add_google_oauth_settings.sql @@ -0,0 +1,4 @@ +ALTER TABLE "AuthSettings" +ADD COLUMN IF NOT EXISTS "googleClientId" text, +ADD COLUMN IF NOT EXISTS "googleClientSecret" text, +ADD COLUMN IF NOT EXISTS "authUrl" text; diff --git a/lib/db/migrations/meta/_journal.json b/lib/db/migrations/meta/_journal.json index 59a70bb7a..8b1692f1e 100644 --- a/lib/db/migrations/meta/_journal.json +++ b/lib/db/migrations/meta/_journal.json @@ -113,6 +113,20 @@ "when": 1769251200000, "tag": "0029_add_lovart_home_copy_settings", "breakpoints": true + }, + { + "idx": 16, + "version": "7", + "when": 1769255400000, + "tag": "0030_add_google_auth_toggle", + "breakpoints": true + }, + { + "idx": 17, + "version": "7", + "when": 1769257800000, + "tag": "0031_add_google_oauth_settings", + "breakpoints": true } ] } diff --git a/lib/db/queries.ts b/lib/db/queries.ts index 0bea0586f..ee845ada5 100644 --- a/lib/db/queries.ts +++ b/lib/db/queries.ts @@ -1605,16 +1605,24 @@ export async function getAuthSettings() { return { dingTalkAppKey: "", dingTalkAppSecret: "", + googleClientId: "", + googleClientSecret: "", + authUrl: process.env.AUTH_URL || "", dingTalkOrganizations: [], isDingTalkEnabled: false, + isGoogleEnabled: false, isRegistrationEnabled: true, }; } catch (_error) { return { dingTalkAppKey: "", dingTalkAppSecret: "", + googleClientId: "", + googleClientSecret: "", + authUrl: process.env.AUTH_URL || "", dingTalkOrganizations: [], isDingTalkEnabled: false, + isGoogleEnabled: false, isRegistrationEnabled: true, }; } diff --git a/lib/db/schema.ts b/lib/db/schema.ts index d424d227b..cf7740915 100644 --- a/lib/db/schema.ts +++ b/lib/db/schema.ts @@ -358,11 +358,15 @@ export const authSettings = pgTable("AuthSettings", { id: uuid("id").primaryKey().notNull().defaultRandom(), dingTalkAppKey: text("dingTalkAppKey"), dingTalkAppSecret: text("dingTalkAppSecret"), + googleClientId: text("googleClientId"), + googleClientSecret: text("googleClientSecret"), + authUrl: text("authUrl"), // 多组织钉钉配置,存储为 JSON 数组:[{ name, appKey, appSecret }] dingTalkOrganizations: jsonb("dingTalkOrganizations").$type< { name: string; appKey: string; appSecret: string }[] | null >(), isDingTalkEnabled: boolean("isDingTalkEnabled").notNull().default(false), + isGoogleEnabled: boolean("isGoogleEnabled").notNull().default(false), isRegistrationEnabled: boolean("isRegistrationEnabled").notNull().default(true), updatedAt: timestamp("updatedAt").notNull().defaultNow(), updatedBy: uuid("updatedBy").references(() => user.id), diff --git a/proxy.ts b/proxy.ts index c2333e5e5..54ebfbc15 100644 --- a/proxy.ts +++ b/proxy.ts @@ -69,6 +69,16 @@ export async function proxy(request: NextRequest) { return NextResponse.next(); } + // 允许访问公开的认证配置(登录弹窗需要) + if (pathname === "/api/auth-settings") { + return NextResponse.next(); + } + + // 允许匿名访问新版首页灵感数据 + if (pathname === "/api/featured" || pathname.startsWith("/api/featured/")) { + return NextResponse.next(); + } + // 允许访问钉钉回调页面(public 文件夹中的文件) if (pathname === "/dingtalk-callback.html") { return NextResponse.next(); @@ -88,8 +98,8 @@ export async function proxy(request: NextRequest) { process.env.AUTH_URL ?? `${request.nextUrl.protocol}//${request.headers.get("host") ?? request.nextUrl.host}`; - // 未登录用户只能访问 /login 和 /register - if (!token && !["/login", "/register"].includes(pathname)) { + // 未登录用户允许访问 /(新版首页)、/login、/register,其余需要登录 + if (!token && !["/", "/login", "/register"].includes(pathname)) { // 对于 API 路由的非 GET 请求,直接返回 401 JSON 响应(不重定向) if (pathname.startsWith("/api/") && request.method !== "GET") { return NextResponse.json(