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