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 { DUMMY_PASSWORD } from "@/lib/constants"; import { createGuestUser, getUser } from "@/lib/db/queries"; import { authConfig } from "./auth.config"; import { db } from "@/lib/db"; import { loginToken } from "@/lib/db/schema"; import { eq, and, gt } from "drizzle-orm"; export type UserType = "guest" | "regular" | "admin"; declare module "next-auth" { interface Session extends DefaultSession { user: { id: string; type: UserType; name?: string | null; } & DefaultSession["user"]; } // biome-ignore lint/nursery/useConsistentTypeDefinitions: "Required" interface User { id?: string; email?: string | null; name?: string | null; type: UserType; } } declare module "next-auth/jwt" { interface JWT extends DefaultJWT { id: string; type: UserType; name?: string | null; } } export const { handlers: { GET, POST }, auth, signIn, signOut, } = NextAuth({ ...authConfig, trustHost: true, providers: [ Credentials({ credentials: {}, async authorize(credentials: any) { console.log("[Auth] Authorize called with credentials:", Object.keys(credentials)); // 1. DingTalk Token 登录逻辑 if (credentials.token) { const { token } = credentials; console.log("[Auth] DingTalk logic triggered with token:", token ? token.substring(0, 8) + "..." : "null"); const now = new Date(); const [validToken] = await db .select() .from(loginToken) .where(and(eq(loginToken.token, token), gt(loginToken.expiresAt, now))) .limit(1); console.log("[Auth] Token lookup result:", validToken ? { email: validToken.email, expiresAt: validToken.expiresAt, isExpired: validToken.expiresAt <= now } : "not found"); if (!validToken) { console.log("[Auth] Token not found or expired, returning null"); return null; } const users = await getUser(validToken.email); console.log("[Auth] User lookup for", validToken.email, ":", users.length, "users found"); if (users.length === 0) { console.log("[Auth] No user found for email, returning null"); return null; } const user = users[0]; console.log("[Auth] User found:", { id: user.id, email: user.email, isActive: user.isActive, role: user.role }); // 检查用户是否被激活 if (!user.isActive) { console.log("[Auth] User is not active, returning null"); return null; } await db.delete(loginToken).where(eq(loginToken.id, validToken.id)); console.log("[Auth] Login token deleted"); const userType: UserType = (user.role as UserType) || "regular"; const returnUser = { id: user.id, email: user.email, name: user.name, type: userType }; console.log("[Auth] Returning user object:", returnUser); return returnUser; } // 2. 邮箱密码登录逻辑 const { email, password } = credentials; const users = await getUser(email); if (users.length === 0) { await compare(password, DUMMY_PASSWORD); return null; } const [user] = users; if (!user.password) { await compare(password, DUMMY_PASSWORD); return null; } const passwordsMatch = await compare(password, user.password); if (!passwordsMatch) { return null; } // 检查用户是否被激活 if (!user.isActive) { return null; } // 从数据库读取用户角色,默认为 regular const userType: UserType = (user.role as UserType) || "regular"; return { id: user.id, email: user.email, name: user.name, type: userType }; }, }), Credentials({ id: "guest", credentials: {}, async authorize() { const [guestUser] = await createGuestUser(); 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; } return session; }, }, });