File size: 5,406 Bytes
69b897d 6c6056a 69b897d 6c6056a 69b897d 6c6056a 69b897d 6c6056a 69b897d |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
const axios = require('axios')
const config = require('../../config/config')
const logger = require('./logger')
const ProxyHelper = require('./proxyHelper')
const WORKOS_CONFIG = config.droid || {}
const WORKOS_DEVICE_AUTHORIZE_URL =
WORKOS_CONFIG.deviceAuthorizeUrl || 'https://api.workos.com/user_management/authorize/device'
const WORKOS_TOKEN_URL =
WORKOS_CONFIG.tokenUrl || 'https://api.workos.com/user_management/authenticate'
const WORKOS_CLIENT_ID = WORKOS_CONFIG.clientId || 'client_01HNM792M5G5G1A2THWPXKFMXB'
const DEFAULT_POLL_INTERVAL = 5
class WorkOSDeviceAuthError extends Error {
constructor(message, code, options = {}) {
super(message)
this.name = 'WorkOSDeviceAuthError'
this.code = code || 'unknown_error'
this.retryAfter = options.retryAfter || null
}
}
/**
* 启动设备码授权流程
* @param {object|null} proxyConfig - 代理配置
* @returns {Promise<object>} WorkOS 返回的数据
*/
async function startDeviceAuthorization(proxyConfig = null) {
const form = new URLSearchParams({
client_id: WORKOS_CLIENT_ID
})
const agent = ProxyHelper.createProxyAgent(proxyConfig)
try {
logger.info('🔐 请求 WorkOS 设备码授权', {
url: WORKOS_DEVICE_AUTHORIZE_URL,
hasProxy: !!agent
})
const axiosConfig = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
timeout: 15000
}
if (agent) {
axiosConfig.httpAgent = agent
axiosConfig.httpsAgent = agent
axiosConfig.proxy = false
}
const response = await axios.post(WORKOS_DEVICE_AUTHORIZE_URL, form.toString(), axiosConfig)
const data = response.data || {}
if (!data.device_code || !data.verification_uri) {
throw new Error('WorkOS 返回数据缺少必要字段 (device_code / verification_uri)')
}
logger.success('✅ 成功获取 WorkOS 设备码授权信息', {
verificationUri: data.verification_uri,
userCode: data.user_code
})
return {
deviceCode: data.device_code,
userCode: data.user_code,
verificationUri: data.verification_uri,
verificationUriComplete: data.verification_uri_complete || data.verification_uri,
expiresIn: data.expires_in || 300,
interval: data.interval || DEFAULT_POLL_INTERVAL
}
} catch (error) {
if (error.response) {
logger.error('❌ WorkOS 设备码授权失败', {
status: error.response.status,
data: error.response.data
})
throw new WorkOSDeviceAuthError(
error.response.data?.error_description ||
error.response.data?.error ||
'WorkOS 设备码授权失败',
error.response.data?.error
)
}
logger.error('❌ 请求 WorkOS 设备码授权异常', {
message: error.message
})
throw new WorkOSDeviceAuthError(error.message)
}
}
/**
* 轮询授权结果
* @param {string} deviceCode - 设备码
* @param {object|null} proxyConfig - 代理配置
* @returns {Promise<object>} WorkOS 返回的 token 数据
*/
async function pollDeviceAuthorization(deviceCode, proxyConfig = null) {
if (!deviceCode) {
throw new WorkOSDeviceAuthError('缺少设备码,无法查询授权结果', 'missing_device_code')
}
const form = new URLSearchParams({
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
device_code: deviceCode,
client_id: WORKOS_CLIENT_ID
})
const agent = ProxyHelper.createProxyAgent(proxyConfig)
try {
const axiosConfig = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
timeout: 15000
}
if (agent) {
axiosConfig.httpAgent = agent
axiosConfig.httpsAgent = agent
axiosConfig.proxy = false
}
const response = await axios.post(WORKOS_TOKEN_URL, form.toString(), axiosConfig)
const data = response.data || {}
if (!data.access_token) {
throw new WorkOSDeviceAuthError('WorkOS 返回结果缺少 access_token', 'missing_access_token')
}
logger.success('🤖 Droid 授权完成,获取到访问令牌', {
hasRefreshToken: !!data.refresh_token
})
return data
} catch (error) {
if (error.response) {
const responseData = error.response.data || {}
const errorCode = responseData.error || `http_${error.response.status}`
const errorDescription =
responseData.error_description || responseData.error || 'WorkOS 授权失败'
if (errorCode === 'authorization_pending' || errorCode === 'slow_down') {
const retryAfter =
Number(responseData.interval) ||
Number(error.response.headers?.['retry-after']) ||
DEFAULT_POLL_INTERVAL
throw new WorkOSDeviceAuthError(errorDescription, errorCode, {
retryAfter
})
}
if (errorCode === 'expired_token') {
throw new WorkOSDeviceAuthError(errorDescription, errorCode)
}
logger.error('❌ WorkOS 设备授权轮询失败', {
status: error.response.status,
data: responseData
})
throw new WorkOSDeviceAuthError(errorDescription, errorCode)
}
logger.error('❌ WorkOS 设备授权轮询异常', {
message: error.message
})
throw new WorkOSDeviceAuthError(error.message)
}
}
module.exports = {
startDeviceAuthorization,
pollDeviceAuthorization,
WorkOSDeviceAuthError
}
|