2025-12-17 20:15:18 +08:00

240 lines
9.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 });
}
}