import { NextRequest, NextResponse } from "next/server"; import { db } from "@/lib/db"; import { getAuthSettings, getUser } from "@/lib/db/queries"; import { loginToken, user } from "@/lib/db/schema"; import { generateUUID } from "@/lib/utils"; import { signIn } from "@/app/(auth)/auth"; import { eq } from "drizzle-orm"; export async function GET(request: NextRequest) { const { searchParams } = new URL(request.url); const code = searchParams.get("code"); const state = searchParams.get("state"); console.log("[DingTalk Callback] Received code:", code?.substring(0, 10) + "...", "state:", state); if (!code) { return NextResponse.json({ success: false, error: "Missing code" }, { status: 400 }); } const settings = await getAuthSettings(); if (!settings.isDingTalkEnabled || !settings.dingTalkAppKey || !settings.dingTalkAppSecret) { return NextResponse.json({ success: false, error: "DingTalk login is not configured" }, { status: 400 }); } try { // 1. 获取 userAccessToken console.log("[DingTalk Callback] Getting user access token..."); const tokenResponse = await fetch("https://api.dingtalk.com/v1.0/oauth2/userAccessToken", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ clientId: settings.dingTalkAppKey, clientSecret: settings.dingTalkAppSecret, code, grantType: "authorization_code", }), }); const tokenData = await tokenResponse.json(); console.log("[DingTalk Callback] Token response:", tokenData.accessToken ? "success" : "failed"); if (!tokenData.accessToken) { console.error("[DingTalk Callback] Token Error:", tokenData); return NextResponse.json({ success: false, error: "Failed to get access token: " + (tokenData.message || JSON.stringify(tokenData)) }, { status: 400 }); } // 2. 获取用户 unionId console.log("[DingTalk Callback] Getting user unionId..."); const userMeResponse = await fetch("https://api.dingtalk.com/v1.0/contact/users/me", { headers: { "x-acs-dingtalk-access-token": tokenData.accessToken, }, }); const userMeData = await userMeResponse.json(); console.log("[DingTalk Callback] User me response:", userMeData.unionId ? "success" : "failed"); if (!userMeData.unionId) { console.error("[DingTalk Callback] User Me Error:", userMeData); return NextResponse.json({ success: false, error: "Failed to get user unionId" }, { status: 400 }); } // 3. 获取应用访问令牌 (用于获取更详细的用户信息) console.log("[DingTalk Callback] Getting app access token..."); const appTokenResponse = await fetch(`https://oapi.dingtalk.com/gettoken?appkey=${settings.dingTalkAppKey}&appsecret=${settings.dingTalkAppSecret}`); const appTokenData = await appTokenResponse.json(); if (!appTokenData.access_token) { console.error("[DingTalk Callback] App Token Error:", appTokenData); return NextResponse.json({ success: false, error: "Failed to get app access token" }, { status: 400 }); } // 4. 根据 unionId 获取 userId console.log("[DingTalk Callback] Getting userId by unionId..."); const userIdResponse = await fetch(`https://oapi.dingtalk.com/topapi/user/getbyunionid?access_token=${appTokenData.access_token}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ unionid: userMeData.unionId }), }); const userIdData = await userIdResponse.json(); console.log("[DingTalk Callback] UserId response:", userIdData); let userInfo: any = { unionId: userMeData.unionId, nick: userMeData.nick, avatarUrl: userMeData.avatarUrl, }; // 如果能获取到 userId,则获取更详细的用户信息 if (userIdData.result && userIdData.result.userid) { console.log("[DingTalk Callback] Getting detailed user info..."); const detailResponse = await fetch(`https://oapi.dingtalk.com/topapi/v2/user/get?access_token=${appTokenData.access_token}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ userid: userIdData.result.userid }), }); const detailData = await detailResponse.json(); if (detailData.result) { // 获取部门名称 let departmentNames: string[] = []; const deptIdList = detailData.result.dept_id_list || []; if (deptIdList.length > 0) { console.log("[DingTalk Callback] Getting department info for dept_ids:", deptIdList); try { // 获取每个部门的详细信息 for (const deptId of deptIdList) { const deptResponse = await fetch(`https://oapi.dingtalk.com/topapi/v2/department/get?access_token=${appTokenData.access_token}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ dept_id: deptId }), }); const deptData = await deptResponse.json(); if (deptData.result && deptData.result.name) { departmentNames.push(deptData.result.name); } } console.log("[DingTalk Callback] Department names:", departmentNames); } catch (deptError) { console.error("[DingTalk Callback] Failed to get department info:", deptError); } } userInfo = { ...userInfo, userId: userIdData.result.userid, name: detailData.result.name, email: detailData.result.email, mobile: detailData.result.mobile, avatar: detailData.result.avatar, jobNumber: detailData.result.job_number, title: detailData.result.title, // 职位 deptIdList: deptIdList, departmentNames: departmentNames, // 部门名称列表 }; } } console.log("[DingTalk Callback] Final user info:", { ...userInfo, mobile: "***", departments: userInfo.departmentNames?.join(", ") || "无部门信息" }); // 5. 查找或创建用户 // 使用 unionId 作为唯一标识,email 可能为空 const email = userInfo.email || `dingtalk_${userInfo.unionId}@dingtalk.local`; console.log("[DingTalk Callback] Checking user:", email); // 检查用户是否存在(直接使用 Drizzle 查询,不通过 getUser) let existingUsers; try { existingUsers = await db .select() .from(user) .where(eq(user.email, email)); console.log("[DingTalk Callback] User found:", existingUsers.length > 0); if (existingUsers.length > 0) { console.log("[DingTalk Callback] User details:", { id: existingUsers[0].id, email: existingUsers[0].email, name: existingUsers[0].name, isActive: existingUsers[0].isActive }); } } catch (error) { console.error("[DingTalk Callback] Error querying user:", error); throw error; } if (existingUsers.length === 0) { // 创建新用户 console.log("[DingTalk Callback] Creating new user:", email, "with name:", userInfo.name || userInfo.nick); try { await db.insert(user).values({ email, password: "", // 钉钉用户无密码 name: userInfo.name || userInfo.nick, // 保存真实姓名或昵称 role: "regular", isActive: true, }); console.log("[DingTalk Callback] User created successfully"); // 重新查询用户 existingUsers = await db .select() .from(user) .where(eq(user.email, email)); } catch (error) { console.error("[DingTalk Callback] Error creating user:", error); throw error; } } else { // 更新现有用户的姓名(如果数据库中为空但钉钉有数据) const existingUser = existingUsers[0]; console.log("[DingTalk Callback] Existing user found, current name:", existingUser.name); if (!existingUser.name && (userInfo.name || userInfo.nick)) { console.log("[DingTalk Callback] Updating user name for:", email, "to:", userInfo.name || userInfo.nick); try { await db.update(user) .set({ name: userInfo.name || userInfo.nick }) .where(eq(user.email, email)); console.log("[DingTalk Callback] User name updated successfully"); } catch (error) { console.error("[DingTalk Callback] Error updating user name:", error); // 不抛出错误,继续登录流程 } } } // 6. 生成临时登录令牌 const token = generateUUID(); await db.insert(loginToken).values({ token, email, expiresAt: new Date(Date.now() + 5 * 60 * 1000), // 5分钟有效期 }); console.log("[DingTalk Callback] Login successful for:", email); // 7. 返回成功响应(给 iframe 中的回调页面) return NextResponse.json({ success: true, message: "登录成功", user: { email, name: userInfo.name || userInfo.nick, avatar: userInfo.avatar || userInfo.avatarUrl, title: userInfo.title, departments: userInfo.departmentNames || [], }, token, }); } catch (error) { console.error("[DingTalk Callback] Error:", error); return NextResponse.json({ success: false, error: "Internal server error: " + (error as Error).message }, { status: 500 }); } }