699 lines
20 KiB
TypeScript
699 lines
20 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server'
|
|
import { prisma } from '@/lib/prisma'
|
|
import {
|
|
getDingtalkAppAccessToken,
|
|
getDingtalkWorkNoticeAppAccessToken,
|
|
} from '@/lib/dingtalk-client'
|
|
import { ensurePerformanceSurveyEmployeeProfileForUser } from '@/lib/performance-survey-report-sync'
|
|
import {
|
|
getAuthSessionCookieCandidates,
|
|
getAuthSessionCookieName,
|
|
resolveSecureAuthCookieFromRequest,
|
|
} from '@/lib/auth-cookie'
|
|
import bcrypt from 'bcryptjs'
|
|
import { encode } from 'next-auth/jwt'
|
|
import type { Role } from '@prisma/client'
|
|
import { randomBytes } from 'crypto'
|
|
import {
|
|
createDingtalkProfileToken,
|
|
DINGTALK_PROFILE_TOKEN_MAX_AGE_SECONDS,
|
|
getDingtalkProfileCookieName,
|
|
getDingtalkProfileCookiePath,
|
|
} from '@/lib/dingtalk-profile-token'
|
|
|
|
type DingtalkUserInfo = {
|
|
name?: string | null
|
|
avatar?: string | null
|
|
mobile?: string | null
|
|
email?: string | null
|
|
}
|
|
|
|
type DingtalkLoginAppSource = 'login' | 'workNotice'
|
|
|
|
type DingtalkNextAuthUser = {
|
|
id: string
|
|
email: string
|
|
username: string
|
|
realName?: string | null
|
|
role: Role
|
|
department: string
|
|
managedDepartment?: string | null
|
|
avatar?: string | null
|
|
isSuperAdmin?: boolean | null
|
|
}
|
|
|
|
let configValidated = false
|
|
|
|
async function createDingtalkOnlyPasswordHash() {
|
|
return bcrypt.hash(randomBytes(32).toString('hex'), 10)
|
|
}
|
|
|
|
function validateDingtalkConfig(
|
|
appSource: DingtalkLoginAppSource = 'login',
|
|
): { clientId: string; clientSecret: string } | null {
|
|
const clientId = appSource === 'workNotice'
|
|
? process.env.DINGTALK_WORK_NOTICE_CLIENT_ID ||
|
|
process.env.DINGTALK_WORK_NOTICE_APP_KEY ||
|
|
process.env.DINGTALK_CLIENT_ID
|
|
: process.env.DINGTALK_CLIENT_ID
|
|
const clientSecret = appSource === 'workNotice'
|
|
? process.env.DINGTALK_WORK_NOTICE_CLIENT_SECRET ||
|
|
process.env.DINGTALK_WORK_NOTICE_APP_SECRET ||
|
|
process.env.DINGTALK_CLIENT_SECRET
|
|
: process.env.DINGTALK_CLIENT_SECRET
|
|
|
|
if (!clientId || !clientSecret) {
|
|
if (!configValidated) {
|
|
console.error('[Dingtalk] 配置错误:缺少必需的环境变量')
|
|
console.error(
|
|
appSource === 'workNotice'
|
|
? '[Dingtalk] 请配置 DINGTALK_WORK_NOTICE_CLIENT_ID 和 DINGTALK_WORK_NOTICE_CLIENT_SECRET'
|
|
: '[Dingtalk] 请配置 DINGTALK_CLIENT_ID 和 DINGTALK_CLIENT_SECRET',
|
|
)
|
|
configValidated = true
|
|
}
|
|
return null
|
|
}
|
|
|
|
if (!configValidated) {
|
|
console.log('[Dingtalk] 钉钉配置已加载')
|
|
configValidated = true
|
|
}
|
|
|
|
return { clientId, clientSecret }
|
|
}
|
|
|
|
function getDingtalkConfig(
|
|
appSource: DingtalkLoginAppSource = 'login',
|
|
): { clientId: string; clientSecret: string } {
|
|
const config = validateDingtalkConfig(appSource)
|
|
if (!config) {
|
|
throw new Error(
|
|
appSource === 'workNotice'
|
|
? '钉钉工作通知免登未配置,请联系管理员配置 DINGTALK_WORK_NOTICE_CLIENT_ID 和 DINGTALK_WORK_NOTICE_CLIENT_SECRET'
|
|
: '钉钉登录未配置,请联系管理员配置 DINGTALK_CLIENT_ID 和 DINGTALK_CLIENT_SECRET',
|
|
)
|
|
}
|
|
return config
|
|
}
|
|
|
|
async function getUserAccessToken(code: string, appSource: DingtalkLoginAppSource = 'login') {
|
|
const config = getDingtalkConfig(appSource)
|
|
const response = await fetch('https://api.dingtalk.com/v1.0/oauth2/userAccessToken', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
clientId: config.clientId,
|
|
clientSecret: config.clientSecret,
|
|
code,
|
|
grantType: 'authorization_code',
|
|
}),
|
|
})
|
|
|
|
if (!response.ok) {
|
|
const errorText = await response.text()
|
|
console.error('获取用户访问令牌HTTP错误:', response.status, errorText)
|
|
throw new Error(`获取用户访问令牌失败: HTTP ${response.status}`)
|
|
}
|
|
|
|
const data = await response.json()
|
|
if (data.code && data.code !== '0') {
|
|
console.error('获取用户访问令牌API错误:', data)
|
|
throw new Error(`获取用户访问令牌失败: ${data.message || '未知错误'}`)
|
|
}
|
|
|
|
return data
|
|
}
|
|
|
|
async function getUnionId(token: string) {
|
|
const response = await fetch('https://api.dingtalk.com/v1.0/contact/users/me', {
|
|
headers: { 'x-acs-dingtalk-access-token': token },
|
|
})
|
|
|
|
if (!response.ok) {
|
|
throw new Error('获取unionId失败')
|
|
}
|
|
|
|
const data = await response.json()
|
|
if (!data.unionId) {
|
|
console.error('获取unionId返回数据异常:', data)
|
|
throw new Error('获取unionId失败: 返回数据中没有unionId')
|
|
}
|
|
|
|
return data.unionId
|
|
}
|
|
|
|
async function getUserIdByUnionId(unionId: string, appToken: string) {
|
|
const response = await fetch(
|
|
`https://oapi.dingtalk.com/topapi/user/getbyunionid?access_token=${appToken}`,
|
|
{
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ unionid: unionId }),
|
|
},
|
|
)
|
|
|
|
if (!response.ok) {
|
|
throw new Error('根据unionId获取userId失败')
|
|
}
|
|
|
|
const data = await response.json()
|
|
if (data.errcode !== 0) {
|
|
console.error('钉钉API错误:', data)
|
|
throw new Error(`获取userId失败: ${data.errmsg || '未知错误'}`)
|
|
}
|
|
|
|
if (!data.result?.userid) {
|
|
console.error('钉钉API返回数据异常:', data)
|
|
throw new Error('获取userId失败: 返回数据格式错误')
|
|
}
|
|
|
|
return data.result.userid
|
|
}
|
|
|
|
async function getUserIdByH5AuthCode(code: string, appToken: string) {
|
|
const response = await fetch(
|
|
`https://oapi.dingtalk.com/topapi/v2/user/getuserinfo?access_token=${appToken}`,
|
|
{
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ code }),
|
|
},
|
|
)
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`通过免登码获取用户失败: HTTP ${response.status}`)
|
|
}
|
|
|
|
const data = await response.json()
|
|
if (data.errcode !== 0) {
|
|
console.error('钉钉免登API错误:', data)
|
|
throw new Error(`通过免登码获取用户失败: ${data.errmsg || '未知错误'}`)
|
|
}
|
|
|
|
const userId = data.result?.userid || data.result?.userId || data.userid || data.userId
|
|
if (!userId) {
|
|
console.error('钉钉免登API返回数据异常:', data)
|
|
throw new Error('通过免登码获取用户失败: 返回数据中没有userId')
|
|
}
|
|
|
|
return String(userId)
|
|
}
|
|
|
|
async function getUserInfo(userId: string, appToken: string): Promise<DingtalkUserInfo> {
|
|
const response = await fetch(
|
|
`https://oapi.dingtalk.com/topapi/v2/user/get?access_token=${appToken}`,
|
|
{
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ userid: userId }),
|
|
},
|
|
)
|
|
|
|
if (!response.ok) {
|
|
throw new Error('获取用户信息失败')
|
|
}
|
|
|
|
const data = await response.json()
|
|
if (data.errcode !== 0) {
|
|
console.error('钉钉API错误:', data)
|
|
throw new Error(`获取用户信息失败: ${data.errmsg || '未知错误'}`)
|
|
}
|
|
|
|
if (!data.result) {
|
|
console.error('钉钉API返回数据异常:', data)
|
|
throw new Error('获取用户信息失败: 返回数据格式错误')
|
|
}
|
|
|
|
return data.result
|
|
}
|
|
|
|
function generateRandomEmail(): string {
|
|
const randomStr = Math.random().toString(36).substring(2, 10)
|
|
return `dingtalk_${randomStr}@temp.com`
|
|
}
|
|
|
|
function warmPerformanceSurveyProfile(user: {
|
|
id: string
|
|
dingtalkUserId: string | null
|
|
email: string
|
|
username: string
|
|
realName: string | null
|
|
}) {
|
|
void ensurePerformanceSurveyEmployeeProfileForUser({
|
|
userId: user.id,
|
|
dingtalkUserId: user.dingtalkUserId,
|
|
email: user.email,
|
|
username: user.username,
|
|
realName: user.realName,
|
|
displayName: user.realName || user.username,
|
|
}).catch((error) => {
|
|
console.warn('[performance-survey] 钉钉登录后预热人员档案失败:', error)
|
|
})
|
|
}
|
|
|
|
async function createAuthenticatedResponse(
|
|
request: NextRequest,
|
|
user: {
|
|
id: string
|
|
email: string
|
|
username: string
|
|
realName?: string | null
|
|
role: Role
|
|
department: string
|
|
managedDepartment?: string | null
|
|
avatar?: string | null
|
|
isSuperAdmin?: boolean | null
|
|
},
|
|
options?: {
|
|
cookieSameSite?: 'lax' | 'none'
|
|
},
|
|
) {
|
|
const secret = process.env.NEXTAUTH_SECRET
|
|
if (!secret) {
|
|
throw new Error('登录会话未配置 NEXTAUTH_SECRET')
|
|
}
|
|
|
|
const maxAge = 7 * 24 * 60 * 60
|
|
const secureCookie = resolveSecureAuthCookieFromRequest(request)
|
|
const cookieName = getAuthSessionCookieName(secureCookie)
|
|
const cookieNames = secureCookie
|
|
? getAuthSessionCookieCandidates(true)
|
|
: [cookieName]
|
|
const sameSite = options?.cookieSameSite === 'none' && secureCookie ? 'none' : 'lax'
|
|
const sessionToken = await encode({
|
|
secret,
|
|
maxAge,
|
|
token: {
|
|
sub: user.id,
|
|
id: user.id,
|
|
email: user.email,
|
|
name: user.username,
|
|
role: user.role,
|
|
avatar: user.avatar || null,
|
|
isSuperAdmin: user.isSuperAdmin ?? false,
|
|
department: user.department || '',
|
|
managedDepartment: user.managedDepartment || null,
|
|
},
|
|
})
|
|
|
|
const response = NextResponse.json({
|
|
success: true,
|
|
needCompleteProfile: false,
|
|
message: '登录成功',
|
|
sessionCreated: true,
|
|
user: {
|
|
id: user.id,
|
|
username: user.username,
|
|
email: user.email,
|
|
realName: user.realName,
|
|
avatar: user.avatar,
|
|
role: user.role,
|
|
department: user.department,
|
|
},
|
|
})
|
|
|
|
response.cookies.set(cookieName, sessionToken, {
|
|
httpOnly: true,
|
|
sameSite,
|
|
secure: secureCookie,
|
|
path: '/',
|
|
maxAge,
|
|
})
|
|
|
|
for (const name of cookieNames) {
|
|
if (name === cookieName) {
|
|
continue
|
|
}
|
|
|
|
response.cookies.set(name, sessionToken, {
|
|
httpOnly: true,
|
|
sameSite,
|
|
secure: secureCookie,
|
|
path: '/',
|
|
maxAge,
|
|
})
|
|
}
|
|
|
|
return response
|
|
}
|
|
|
|
async function createOrUpdateUserSession(
|
|
request: NextRequest,
|
|
userId: string,
|
|
userInfo: DingtalkUserInfo,
|
|
options?: {
|
|
cookieSameSite?: 'lax' | 'none'
|
|
},
|
|
) {
|
|
const displayName = String(userInfo.name || '').trim()
|
|
if (!displayName) {
|
|
throw new Error('钉钉返回的用户名称为空')
|
|
}
|
|
|
|
let user = await prisma.user.findFirst({
|
|
where: { dingtalkUserId: userId },
|
|
})
|
|
|
|
let needCompleteProfile = false
|
|
if (!user) {
|
|
const existingUserByName = await prisma.user.findUnique({
|
|
where: { username: displayName },
|
|
})
|
|
|
|
if (existingUserByName && !existingUserByName.dingtalkUserId) {
|
|
console.log('找到同名用户,自动关联钉钉账号:', existingUserByName.username)
|
|
user = await prisma.user.update({
|
|
where: { id: existingUserByName.id },
|
|
data: {
|
|
dingtalkUserId: userId,
|
|
loginType: 'dingtalk',
|
|
realName: displayName,
|
|
avatar: userInfo.avatar || existingUserByName.avatar,
|
|
phone: userInfo.mobile || existingUserByName.phone,
|
|
},
|
|
})
|
|
console.log('账号关联成功:', user.username)
|
|
} else {
|
|
console.log('创建新用户(使用钉钉姓名):', displayName)
|
|
|
|
let emailToUse = userInfo.email || generateRandomEmail()
|
|
const emailExists = await prisma.user.findUnique({ where: { email: emailToUse } })
|
|
if (emailExists) {
|
|
emailToUse = generateRandomEmail()
|
|
}
|
|
|
|
user = await prisma.user.create({
|
|
data: {
|
|
username: displayName,
|
|
email: emailToUse,
|
|
password: await createDingtalkOnlyPasswordHash(),
|
|
realName: displayName,
|
|
avatar: userInfo.avatar || null,
|
|
phone: userInfo.mobile || null,
|
|
department: '',
|
|
role: 'USER',
|
|
isActive: false,
|
|
status: 'pending',
|
|
loginType: 'dingtalk',
|
|
dingtalkUserId: userId,
|
|
},
|
|
})
|
|
|
|
needCompleteProfile = true
|
|
console.log('用户创建成功:', user.id)
|
|
}
|
|
} else {
|
|
console.log('更新现有用户:', user.username)
|
|
user = await prisma.user.update({
|
|
where: { id: user.id },
|
|
data: {
|
|
realName: displayName,
|
|
avatar: userInfo.avatar || user.avatar,
|
|
phone: userInfo.mobile || user.phone,
|
|
},
|
|
})
|
|
}
|
|
|
|
if (needCompleteProfile) {
|
|
warmPerformanceSurveyProfile(user)
|
|
const response = NextResponse.json({
|
|
success: true,
|
|
needCompleteProfile: true,
|
|
message: '请选择您的部门',
|
|
user: {
|
|
id: user.id,
|
|
username: user.username,
|
|
email: user.email,
|
|
realName: user.realName,
|
|
avatar: user.avatar,
|
|
role: user.role,
|
|
department: user.department,
|
|
isActive: user.isActive,
|
|
},
|
|
})
|
|
const secureCookie = resolveSecureAuthCookieFromRequest(request)
|
|
response.cookies.set(
|
|
getDingtalkProfileCookieName(request),
|
|
await createDingtalkProfileToken(user.id),
|
|
{
|
|
httpOnly: true,
|
|
sameSite: options?.cookieSameSite === 'none' && secureCookie ? 'none' : 'lax',
|
|
secure: secureCookie,
|
|
path: getDingtalkProfileCookiePath(),
|
|
maxAge: DINGTALK_PROFILE_TOKEN_MAX_AGE_SECONDS,
|
|
},
|
|
)
|
|
return response
|
|
}
|
|
|
|
const userStatus = (user as any).status || (user.isActive ? 'active' : 'pending')
|
|
const isUserActive = userStatus === 'active'
|
|
|
|
if (!isUserActive) {
|
|
warmPerformanceSurveyProfile(user)
|
|
return NextResponse.json(
|
|
{
|
|
success: false,
|
|
message: '您的账号尚未激活,请联系管理员激活后再登录',
|
|
},
|
|
{ status: 403 },
|
|
)
|
|
}
|
|
|
|
if (userStatus === 'suspended' || userStatus === 'archived') {
|
|
return NextResponse.json(
|
|
{
|
|
success: false,
|
|
message: '您的账号已被暂停或归档,无法登录',
|
|
},
|
|
{ status: 403 },
|
|
)
|
|
}
|
|
|
|
const clientIp =
|
|
request.headers.get('x-forwarded-for') ||
|
|
request.headers.get('x-real-ip') ||
|
|
null
|
|
|
|
await prisma.user.update({
|
|
where: { id: user.id },
|
|
data: {
|
|
lastLoginAt: new Date(),
|
|
lastLoginIp: clientIp,
|
|
loginCount: { increment: 1 },
|
|
status: 'active',
|
|
isActive: true,
|
|
},
|
|
}).catch((error) => {
|
|
console.error('记录登录审计信息失败:', error)
|
|
})
|
|
|
|
warmPerformanceSurveyProfile(user)
|
|
|
|
return createAuthenticatedResponse(request, {
|
|
id: user.id,
|
|
email: user.email,
|
|
username: user.username,
|
|
realName: user.realName,
|
|
role: user.role,
|
|
department: user.department,
|
|
managedDepartment: user.managedDepartment,
|
|
avatar: user.avatar,
|
|
isSuperAdmin: (user as any).isSuperAdmin ?? false,
|
|
}, options)
|
|
}
|
|
|
|
function getAppAccessToken(appSource: DingtalkLoginAppSource) {
|
|
return appSource === 'workNotice'
|
|
? getDingtalkWorkNoticeAppAccessToken()
|
|
: getDingtalkAppAccessToken()
|
|
}
|
|
|
|
async function resolveDingtalkUserForSession(
|
|
userId: string,
|
|
userInfo: DingtalkUserInfo,
|
|
clientIp?: string | null,
|
|
): Promise<DingtalkNextAuthUser> {
|
|
const displayName = String(userInfo.name || '').trim()
|
|
if (!displayName) {
|
|
throw new Error('钉钉返回的用户名称为空')
|
|
}
|
|
|
|
let user = await prisma.user.findFirst({
|
|
where: { dingtalkUserId: userId },
|
|
})
|
|
|
|
if (!user) {
|
|
const existingUserByName = await prisma.user.findUnique({
|
|
where: { username: displayName },
|
|
})
|
|
|
|
if (existingUserByName && !existingUserByName.dingtalkUserId) {
|
|
console.log('找到同名用户,自动关联钉钉账号:', existingUserByName.username)
|
|
user = await prisma.user.update({
|
|
where: { id: existingUserByName.id },
|
|
data: {
|
|
dingtalkUserId: userId,
|
|
loginType: 'dingtalk',
|
|
realName: displayName,
|
|
avatar: userInfo.avatar || existingUserByName.avatar,
|
|
phone: userInfo.mobile || existingUserByName.phone,
|
|
},
|
|
})
|
|
console.log('账号关联成功:', user.username)
|
|
} else {
|
|
console.log('创建新用户(使用钉钉姓名):', displayName)
|
|
|
|
let emailToUse = userInfo.email || generateRandomEmail()
|
|
const emailExists = await prisma.user.findUnique({ where: { email: emailToUse } })
|
|
if (emailExists) {
|
|
emailToUse = generateRandomEmail()
|
|
}
|
|
|
|
user = await prisma.user.create({
|
|
data: {
|
|
username: displayName,
|
|
email: emailToUse,
|
|
password: await createDingtalkOnlyPasswordHash(),
|
|
realName: displayName,
|
|
avatar: userInfo.avatar || null,
|
|
phone: userInfo.mobile || null,
|
|
department: '',
|
|
role: 'USER',
|
|
isActive: false,
|
|
status: 'pending',
|
|
loginType: 'dingtalk',
|
|
dingtalkUserId: userId,
|
|
},
|
|
})
|
|
|
|
warmPerformanceSurveyProfile(user)
|
|
throw new Error('您的账号尚未激活,请联系管理员激活后再登录')
|
|
}
|
|
} else {
|
|
console.log('更新现有用户:', user.username)
|
|
user = await prisma.user.update({
|
|
where: { id: user.id },
|
|
data: {
|
|
realName: displayName,
|
|
avatar: userInfo.avatar || user.avatar,
|
|
phone: userInfo.mobile || user.phone,
|
|
},
|
|
})
|
|
}
|
|
|
|
const userStatus = (user as any).status || (user.isActive ? 'active' : 'pending')
|
|
const isUserActive = userStatus === 'active'
|
|
|
|
if (!isUserActive) {
|
|
warmPerformanceSurveyProfile(user)
|
|
throw new Error('您的账号尚未激活,请联系管理员激活后再登录')
|
|
}
|
|
|
|
if (userStatus === 'suspended' || userStatus === 'archived') {
|
|
throw new Error('您的账号已被暂停或归档,无法登录')
|
|
}
|
|
|
|
await prisma.user.update({
|
|
where: { id: user.id },
|
|
data: {
|
|
lastLoginAt: new Date(),
|
|
lastLoginIp: clientIp || null,
|
|
loginCount: { increment: 1 },
|
|
status: 'active',
|
|
isActive: true,
|
|
},
|
|
}).catch((error) => {
|
|
console.error('记录登录审计信息失败:', error)
|
|
})
|
|
|
|
warmPerformanceSurveyProfile(user)
|
|
|
|
return {
|
|
id: user.id,
|
|
email: user.email,
|
|
username: user.username,
|
|
realName: user.realName,
|
|
role: user.role,
|
|
department: user.department,
|
|
managedDepartment: user.managedDepartment,
|
|
avatar: user.avatar,
|
|
isSuperAdmin: (user as any).isSuperAdmin ?? false,
|
|
}
|
|
}
|
|
|
|
export async function authorizeDingtalkH5UserForNextAuth(
|
|
code: string,
|
|
options?: { appSource?: DingtalkLoginAppSource; clientIp?: string | null },
|
|
) {
|
|
console.log('开始钉钉H5免登NextAuth登录流程')
|
|
const appSource = options?.appSource || 'login'
|
|
|
|
const appAccessToken = await getAppAccessToken(appSource)
|
|
console.log('获取应用访问令牌成功')
|
|
|
|
const userId = await getUserIdByH5AuthCode(code, appAccessToken)
|
|
console.log('通过免登码获取userId成功:', userId)
|
|
|
|
const userInfo = await getUserInfo(userId, appAccessToken)
|
|
console.log('获取用户信息成功:', userInfo.name)
|
|
|
|
return resolveDingtalkUserForSession(userId, userInfo, options?.clientIp || null)
|
|
}
|
|
|
|
export async function loginDingtalkUserByOAuthCode(
|
|
request: NextRequest,
|
|
code: string,
|
|
options?: { appSource?: DingtalkLoginAppSource },
|
|
) {
|
|
console.log('开始钉钉OAuth登录流程')
|
|
const appSource = options?.appSource || 'login'
|
|
|
|
const tokenData = await getUserAccessToken(code, appSource)
|
|
if (!tokenData?.accessToken) {
|
|
console.error('获取用户访问令牌返回数据异常:', tokenData)
|
|
return NextResponse.json(
|
|
{ success: false, message: '获取用户访问令牌失败:返回数据格式错误' },
|
|
{ status: 500 },
|
|
)
|
|
}
|
|
|
|
console.log('获取用户访问令牌成功')
|
|
const appAccessToken = await getAppAccessToken(appSource)
|
|
console.log('获取应用访问令牌成功')
|
|
|
|
const unionId = await getUnionId(tokenData.accessToken)
|
|
console.log('获取unionId成功:', unionId)
|
|
|
|
const userId = await getUserIdByUnionId(unionId, appAccessToken)
|
|
console.log('获取userId成功:', userId)
|
|
|
|
const userInfo = await getUserInfo(userId, appAccessToken)
|
|
console.log('获取用户信息成功:', userInfo.name)
|
|
|
|
return createOrUpdateUserSession(request, userId, userInfo)
|
|
}
|
|
|
|
export async function loginDingtalkUserByH5AuthCode(
|
|
request: NextRequest,
|
|
code: string,
|
|
options?: { appSource?: DingtalkLoginAppSource },
|
|
) {
|
|
console.log('开始钉钉H5免登流程')
|
|
const appSource = options?.appSource || 'login'
|
|
|
|
const appAccessToken = await getAppAccessToken(appSource)
|
|
console.log('获取应用访问令牌成功')
|
|
|
|
const userId = await getUserIdByH5AuthCode(code, appAccessToken)
|
|
console.log('通过免登码获取userId成功:', userId)
|
|
|
|
const userInfo = await getUserInfo(userId, appAccessToken)
|
|
console.log('获取用户信息成功:', userInfo.name)
|
|
|
|
return createOrUpdateUserSession(request, userId, userInfo, {
|
|
cookieSameSite: 'none',
|
|
})
|
|
}
|