import { NextRequest, NextResponse } from "next/server"; import { z } from "zod"; import { db } from "@/lib/db"; import { authSettings } from "@/lib/db/schema"; import { eq } from "drizzle-orm"; import { auth } from "@/app/(auth)/auth"; // GET /api/auth-settings - 获取认证配置 export async function GET() { try { const session = await auth(); const isAdmin = session?.user?.type === "admin"; const [settings] = await db.select().from(authSettings).limit(1); const resolvedGoogleClientId = settings?.googleClientId || process.env.GOOGLE_CLIENT_ID || ""; const resolvedGoogleClientSecret = settings?.googleClientSecret || process.env.GOOGLE_CLIENT_SECRET || ""; const resolvedAuthUrl = settings?.authUrl || process.env.AUTH_URL || ""; const isGoogleConfigured = Boolean( resolvedGoogleClientId && resolvedGoogleClientSecret ); // 如果没有配置记录,返回默认值 if (!settings) { return NextResponse.json({ dingTalkAppKey: "", dingTalkAppSecret: "", // 即使是空的,非管理员也不应该看到这个字段名,但因为是空字符串,问题不大 dingTalkOrganizations: [], isDingTalkEnabled: false, isGoogleEnabled: false, googleClientId: "", googleClientSecret: "", authUrl: process.env.AUTH_URL || "", isGoogleConfigured, isRegistrationEnabled: true, isEmailVerificationEnabled: false, emailVerificationExpiresMinutes: 10, resendApiKey: "", resendFromEmail: "", isEmailServiceConfigured: false, }); } if (isAdmin) { return NextResponse.json({ ...settings, googleClientId: resolvedGoogleClientId, googleClientSecret: resolvedGoogleClientSecret, authUrl: resolvedAuthUrl, isGoogleConfigured, dingTalkOrganizations: settings.dingTalkOrganizations ?? [], isEmailVerificationEnabled: settings.isEmailVerificationEnabled, emailVerificationExpiresMinutes: settings.emailVerificationExpiresMinutes, resendApiKey: settings.resendApiKey || "", resendFromEmail: settings.resendFromEmail || "", isEmailServiceConfigured: Boolean( (settings.resendApiKey || "").trim() && (settings.resendFromEmail || "").trim() ), }); } // 非管理员,脱敏返回,只暴露组织名称 const organizations = settings.dingTalkOrganizations?.map((org) => ({ name: org.name, })) ?? []; return NextResponse.json({ dingTalkAppKey: settings.dingTalkAppKey, isDingTalkEnabled: settings.isDingTalkEnabled, isGoogleEnabled: settings.isGoogleEnabled, authUrl: resolvedAuthUrl, isGoogleConfigured, isRegistrationEnabled: settings.isRegistrationEnabled, isEmailVerificationEnabled: settings.isEmailVerificationEnabled, emailVerificationExpiresMinutes: settings.emailVerificationExpiresMinutes, isEmailServiceConfigured: Boolean( (settings.resendApiKey || "").trim() && (settings.resendFromEmail || "").trim() ), organizations, // 不返回 dingTalkAppSecret 以及各组织的密钥 }); } catch (error) { console.error("[Auth Settings API] Get error:", error); return NextResponse.json( { error: "Failed to fetch auth settings" }, { status: 500 } ); } } const orgSchema = z.object({ name: z.string().min(1), appKey: z.string().min(1), appSecret: z.string().min(1), }); const updateSchema = z.object({ dingTalkAppKey: z.string().optional(), dingTalkAppSecret: z.string().optional(), dingTalkOrganizations: z.array(orgSchema).optional(), isDingTalkEnabled: z.boolean().optional(), isGoogleEnabled: z.boolean().optional(), googleClientId: z.string().optional(), googleClientSecret: z.string().optional(), authUrl: z.string().optional(), isRegistrationEnabled: z.boolean().optional(), isEmailVerificationEnabled: z.boolean().optional(), emailVerificationExpiresMinutes: z.number().int().min(3).max(30).optional(), resendApiKey: z.string().optional(), resendFromEmail: z.string().optional(), }); // PUT /api/auth-settings - 更新认证配置(仅管理员) export async function PUT(request: NextRequest) { try { const session = await auth(); if (!session?.user) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } if (session.user.type !== "admin") { return NextResponse.json({ error: "Forbidden" }, { status: 403 }); } const body = await request.json(); const data = updateSchema.parse(body); // 查找现有配置 const [existing] = await db.select().from(authSettings).limit(1); const organizations = data.dingTalkOrganizations ?? existing?.dingTalkOrganizations ?? []; const primaryOrg = organizations[0] as | { name: string; appKey: string; appSecret: string } | undefined; const resolvedAppKey = data.dingTalkAppKey ?? primaryOrg?.appKey ?? existing?.dingTalkAppKey ?? null; const resolvedAppSecret = data.dingTalkAppSecret ?? primaryOrg?.appSecret ?? existing?.dingTalkAppSecret ?? null; const resolvedGoogleClientId = data.googleClientId ?? existing?.googleClientId ?? ""; const resolvedGoogleClientSecret = data.googleClientSecret ?? existing?.googleClientSecret ?? ""; const resolvedAuthUrl = data.authUrl ?? existing?.authUrl ?? ""; const resolvedResendApiKey = String( data.resendApiKey ?? existing?.resendApiKey ?? "" ).trim(); const resolvedResendFromEmail = String( data.resendFromEmail ?? existing?.resendFromEmail ?? "" ).trim(); if (existing) { // 更新现有配置 const [updated] = await db .update(authSettings) .set({ dingTalkAppKey: resolvedAppKey, dingTalkAppSecret: resolvedAppSecret, googleClientId: resolvedGoogleClientId, googleClientSecret: resolvedGoogleClientSecret, authUrl: resolvedAuthUrl, dingTalkOrganizations: organizations, isDingTalkEnabled: data.isDingTalkEnabled ?? existing.isDingTalkEnabled, isGoogleEnabled: data.isGoogleEnabled ?? existing.isGoogleEnabled, isRegistrationEnabled: data.isRegistrationEnabled ?? existing.isRegistrationEnabled, isEmailVerificationEnabled: data.isEmailVerificationEnabled ?? existing.isEmailVerificationEnabled, emailVerificationExpiresMinutes: data.emailVerificationExpiresMinutes ?? existing.emailVerificationExpiresMinutes, resendApiKey: resolvedResendApiKey, resendFromEmail: resolvedResendFromEmail, updatedAt: new Date(), updatedBy: session.user.id, }) .where(eq(authSettings.id, existing.id)) .returning(); return NextResponse.json(updated); } // 创建新配置 const [created] = await db .insert(authSettings) .values({ dingTalkAppKey: resolvedAppKey, dingTalkAppSecret: resolvedAppSecret, googleClientId: resolvedGoogleClientId, googleClientSecret: resolvedGoogleClientSecret, authUrl: resolvedAuthUrl, dingTalkOrganizations: organizations, isDingTalkEnabled: data.isDingTalkEnabled || false, isGoogleEnabled: data.isGoogleEnabled || false, isRegistrationEnabled: data.isRegistrationEnabled ?? true, isEmailVerificationEnabled: data.isEmailVerificationEnabled ?? false, emailVerificationExpiresMinutes: data.emailVerificationExpiresMinutes ?? 10, resendApiKey: resolvedResendApiKey, resendFromEmail: resolvedResendFromEmail, updatedBy: session.user.id, }) .returning(); return NextResponse.json(created); } catch (error) { console.error("[Auth Settings API] Update error:", error); if (error instanceof z.ZodError) { return NextResponse.json( { error: "Invalid request data", details: error.errors }, { status: 400 } ); } return NextResponse.json( { error: "Failed to update auth settings" }, { status: 500 } ); } }