import NextAuth from "next-auth"; import Credentials from "next-auth/providers/credentials"; import { eq } from "drizzle-orm"; import { db } from "@/lib/db"; import { users, userAccounts } from "@/lib/db/schema"; import { verifyPassword } from "./password"; import { authConfig } from "./config"; import type { Provider } from "next-auth/providers"; // 动态构建 Providers 列表 const providers: Provider[] = [ Credentials({ name: "Credentials", credentials: { email: { label: "Email", type: "email" }, password: { label: "Password", type: "password" }, }, async authorize(credentials) { if (!credentials?.email || !credentials?.password) { return null; } const email = String(credentials.email).toLowerCase().trim(); const password = String(credentials.password); const [user] = await db .select() .from(users) .where(eq(users.email, email)) .limit(1); if (!user || !user.passwordHash || !user.isActive) { return null; } const isValid = await verifyPassword(password, user.passwordHash); if (!isValid) { return null; } return { id: user.id, name: user.name, email: user.email, image: user.avatar, }; }, }), ]; // 如果配置了 OIDC,且 AUTH_OIDC_ENABLED 为 true,则动态注册通用 OIDC 提供商 const isOidcEnabled = process.env.AUTH_OIDC_ENABLED === "true" && Boolean(process.env.AUTH_OIDC_ISSUER) && Boolean(process.env.AUTH_OIDC_CLIENT_ID); if (isOidcEnabled) { providers.push({ id: "oidc", name: process.env.AUTH_OIDC_NAME || "SSO 统一身份认证", // Laysense currently returns nonce: "" even when no nonce was requested, // causing Auth.js to reject the ID Token. Keep the standard OIDC flow here // and leave it disabled in .env.local until the identity provider is fixed. type: "oidc", issuer: process.env.AUTH_OIDC_ISSUER!.replace(/\/$/, ""), clientId: process.env.AUTH_OIDC_CLIENT_ID, clientSecret: process.env.AUTH_OIDC_CLIENT_SECRET, // Laysense requires an OAuth state parameter to correlate the callback. checks: ["state"], authorization: { params: { scope: process.env.AUTH_OIDC_SCOPES || "openid profile email", }, }, }); } export const { handlers, signIn, signOut, auth } = NextAuth({ ...authConfig, providers, session: { strategy: "jwt", maxAge: 30 * 24 * 60 * 60, // 30 天 }, callbacks: { ...authConfig.callbacks, async signIn({ user, account }) { if (!account || account.type === "credentials") { return true; } // 处理 OIDC / OAuth 登录与本地用户的关联或新建 const email = user.email?.toLowerCase().trim(); if (!email) { return false; } // 1. 查询用户是否已存在 let [existingUser] = await db .select() .from(users) .where(eq(users.email, email)) .limit(1); if (!existingUser) { // 创建新用户 const [newUser] = await db .insert(users) .values({ name: user.name || email.split("@")[0], email: email, avatar: user.image || null, isActive: true, }) .returning(); existingUser = newUser; } user.id = existingUser.id; // 2. 查询是否已记录该 provider 的账户绑定 const [existingAccount] = await db .select() .from(userAccounts) .where( eq(userAccounts.providerAccountId, account.providerAccountId) ) .limit(1); if (!existingAccount) { await db.insert(userAccounts).values({ userId: existingUser.id, provider: account.provider, providerAccountId: account.providerAccountId, refreshToken: account.refresh_token, accessToken: account.access_token, expiresAt: account.expires_at ? new Date(account.expires_at * 1000) : null, tokenType: account.token_type, scope: account.scope, idToken: account.id_token, }); } return true; }, }, });