gongxiangp2/lib/dingtalk-oauth.ts
2026-07-17 18:55:37 +08:00

100 lines
2.4 KiB
TypeScript

import { getPublicAppOrigin } from '@/lib/public-app-url'
import { normalizeInternalRedirectPath } from '@/lib/safe-redirect'
const FALLBACK_DINGTALK_CLIENT_ID = 'ding1bs1gxwrcfu16x0f'
export const DINGTALK_DIRECT_LOGIN_SOURCE = 'work_notice_direct'
export function getDingtalkOAuthClientId() {
return (
process.env.DINGTALK_CLIENT_ID ||
process.env.DINGTALK_APP_KEY ||
process.env.NEXT_PUBLIC_DINGTALK_CLIENT_ID ||
FALLBACK_DINGTALK_CLIENT_ID
)
}
export function encodeDingtalkOAuthState(
redirectPath: unknown,
origin = getPublicAppOrigin(),
options?: { source?: string },
) {
const payload: Record<string, string> = {
origin,
redirect: normalizeInternalRedirectPath(redirectPath),
}
if (options?.source) {
payload.source = options.source
}
return JSON.stringify(payload)
}
export function parseDingtalkOAuthState(value: unknown) {
let raw = String(value ?? '').trim()
if (!raw) {
return {
origin: '',
redirectPath: '/',
source: '',
nonce: '',
handoffId: '',
}
}
try {
raw = decodeURIComponent(raw)
} catch {
// URLSearchParams 已经会解码,遇到普通 JSON 字符串时这里可以安全忽略。
}
try {
const data = JSON.parse(raw)
return {
origin: typeof data?.origin === 'string' ? data.origin : '',
redirectPath: normalizeInternalRedirectPath(data?.redirect),
source: typeof data?.source === 'string' ? data.source : '',
nonce: typeof data?.nonce === 'string' ? data.nonce : '',
handoffId: typeof data?.handoffId === 'string' ? data.handoffId : '',
}
} catch {
return {
origin: '',
redirectPath: '/',
source: '',
nonce: '',
handoffId: '',
}
}
}
export function buildDingtalkOAuthUrl({
redirectPath,
callbackPath = '/api/dingtalk/callback',
clientId,
prompt,
source,
}: {
redirectPath: unknown
callbackPath?: string
clientId?: string
prompt?: string
source?: string
}) {
const publicOrigin = getPublicAppOrigin()
const redirectUrl = `${publicOrigin}${callbackPath}`
const params = new URLSearchParams({
redirect_uri: redirectUrl,
client_id: clientId || getDingtalkOAuthClientId(),
scope: 'openid',
response_type: 'code',
state: encodeDingtalkOAuthState(redirectPath, publicOrigin, { source }),
})
if (prompt) {
params.set('prompt', prompt)
}
return `https://login.dingtalk.com/oauth2/auth?${params.toString()}`
}