@cloudbase/toolbox 0.7.17 → 0.7.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/auth/index.d.ts +6 -0
- package/lib/auth/index.js +2 -2
- package/lib/auth/oauth.d.ts +6 -0
- package/lib/auth/oauth.js +68 -28
- package/package.json +1 -1
package/lib/auth/index.d.ts
CHANGED
|
@@ -47,6 +47,12 @@ export interface WebAuthOptions {
|
|
|
47
47
|
getOAuthEndpoint?: DeviceFlowOptions['getOAuthEndpoint'];
|
|
48
48
|
/** 静默模式,不打印任何日志 */
|
|
49
49
|
silent?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* 自定义授权流程模式
|
|
52
|
+
* 为 true 时,device/code 和 token 接口的返回值没有外层 { code, result, reqId } 包装,
|
|
53
|
+
* 直接就是 result 本体;同时 verification_uri 直接使用接口返回值,不再由客户端拼接。
|
|
54
|
+
*/
|
|
55
|
+
custom?: DeviceFlowOptions['custom'];
|
|
50
56
|
}
|
|
51
57
|
export interface LoginByApiSecretOptions {
|
|
52
58
|
/**
|
package/lib/auth/index.js
CHANGED
|
@@ -82,7 +82,7 @@ class AuthSupervisor {
|
|
|
82
82
|
*/
|
|
83
83
|
loginByWebAuth(options = {}) {
|
|
84
84
|
return __awaiter(this, void 0, void 0, function* () {
|
|
85
|
-
const { getAuthUrl, throwError, noBrowser, callbackTimeout, flow = 'device', client_id, onDeviceCode, getOAuthEndpoint, silent } = options;
|
|
85
|
+
const { getAuthUrl, throwError, noBrowser, callbackTimeout, flow = 'device', client_id, onDeviceCode, getOAuthEndpoint, silent, custom } = options;
|
|
86
86
|
if (this.cacheCredential && this.needCache && !this.isCacheExpire()) {
|
|
87
87
|
return this.cacheCredential;
|
|
88
88
|
}
|
|
@@ -100,7 +100,7 @@ class AuthSupervisor {
|
|
|
100
100
|
});
|
|
101
101
|
}
|
|
102
102
|
else {
|
|
103
|
-
credential = yield (0, oauth_1.getAuthTokenByDeviceFlow)({ client_id, onDeviceCode, getOAuthEndpoint, getAuthUrl, silent });
|
|
103
|
+
credential = yield (0, oauth_1.getAuthTokenByDeviceFlow)({ client_id, onDeviceCode, getOAuthEndpoint, getAuthUrl, silent, custom });
|
|
104
104
|
}
|
|
105
105
|
credential = (0, common_1.resolveCredential)(credential);
|
|
106
106
|
try {
|
package/lib/auth/oauth.d.ts
CHANGED
|
@@ -13,5 +13,11 @@ export interface DeviceFlowOptions {
|
|
|
13
13
|
getAuthUrl?: (rawUrl: string) => string;
|
|
14
14
|
/** 静默模式,不打印任何日志 */
|
|
15
15
|
silent?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* 自定义授权流程模式
|
|
18
|
+
* 为 true 时,device/code 和 token 接口的返回值没有外层 { code, result, reqId } 包装,
|
|
19
|
+
* 直接就是 result 本体;同时 verification_uri 直接使用接口返回值,不再由客户端拼接。
|
|
20
|
+
*/
|
|
21
|
+
custom?: boolean;
|
|
16
22
|
}
|
|
17
23
|
export declare function getAuthTokenByDeviceFlow(options?: DeviceFlowOptions): Promise<Credential>;
|
package/lib/auth/oauth.js
CHANGED
|
@@ -51,25 +51,64 @@ function sleep(ms) {
|
|
|
51
51
|
setTimeout(resolve, ms);
|
|
52
52
|
});
|
|
53
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* 将接口原始返回值归一化为裸数据。
|
|
56
|
+
* - 标准模式:从 { code, result, reqId } 中解包,校验 code
|
|
57
|
+
* - custom 模式:返回值本身即为裸数据,直接透传
|
|
58
|
+
*/
|
|
59
|
+
function unwrapDeviceCodeResponse(raw, custom) {
|
|
60
|
+
if (custom) {
|
|
61
|
+
if (!raw || !raw.device_code) {
|
|
62
|
+
throw new error_1.CloudBaseError('Device Flow fetchDeviceCode failed (custom mode): 返回数据异常');
|
|
63
|
+
}
|
|
64
|
+
return { data: raw };
|
|
65
|
+
}
|
|
66
|
+
const resp = raw;
|
|
67
|
+
if (resp.code !== SUCCESS_CODE) {
|
|
68
|
+
throw new error_1.CloudBaseError('Device Flow fetchDeviceCode failed', {
|
|
69
|
+
code: resp.code,
|
|
70
|
+
requestId: resp.reqId,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return { data: resp.result, reqId: resp.reqId };
|
|
74
|
+
}
|
|
75
|
+
function unwrapTokenResponse(raw, custom) {
|
|
76
|
+
if (custom) {
|
|
77
|
+
return { data: raw };
|
|
78
|
+
}
|
|
79
|
+
const resp = raw;
|
|
80
|
+
// 标准模式下若外层 code 异常且无 result,直接抛错
|
|
81
|
+
if (resp.code !== SUCCESS_CODE && !resp.result) {
|
|
82
|
+
throw new error_1.CloudBaseError(`Device Flow 授权失败,错误码: ${resp.code}`, {
|
|
83
|
+
code: resp.code,
|
|
84
|
+
requestId: resp.reqId,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return { data: resp.result, reqId: resp.reqId };
|
|
88
|
+
}
|
|
54
89
|
function getAuthTokenByDeviceFlow(options = {}) {
|
|
55
90
|
return __awaiter(this, void 0, void 0, function* () {
|
|
56
|
-
const { client_id, onDeviceCode, getOAuthEndpoint, getAuthUrl, silent } = options;
|
|
91
|
+
const { client_id, onDeviceCode, getOAuthEndpoint, getAuthUrl, silent, custom } = options;
|
|
57
92
|
const endpoint = getOAuthEndpoint ? getOAuthEndpoint(OAUTH_ENDPOINT) : OAUTH_ENDPOINT;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
93
|
+
// ---- 获取 device code ----
|
|
94
|
+
const deviceCodeRaw = yield fetchDeviceCode({ client_id, endpoint });
|
|
95
|
+
const { data: deviceCodeData } = unwrapDeviceCodeResponse(deviceCodeRaw, !!custom);
|
|
96
|
+
const { device_code, user_code, expires_in, interval } = deviceCodeData;
|
|
97
|
+
// ---- 构造 verification_uri ----
|
|
98
|
+
let verification_uri;
|
|
99
|
+
if (custom) {
|
|
100
|
+
// custom 模式直接使用接口返回的 verification_uri
|
|
101
|
+
const rawUri = deviceCodeData.verification_uri || '';
|
|
102
|
+
verification_uri = getAuthUrl ? getAuthUrl(rawUri) : rawUri;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
// 标准模式由客户端拼接 verification_uri
|
|
106
|
+
const defaultVerificationUri = `${web_auth_1.CLI_AUTH_BASE_URL}#/cli-auth`;
|
|
107
|
+
// from=cli 后续考虑改用 client_id,因为 toolbox 作为公共依赖,可能被不同 client 所引用
|
|
108
|
+
const rawVerificationUri = `${defaultVerificationUri}?user_code=${user_code}&from=cli&flow=device`;
|
|
109
|
+
verification_uri = getAuthUrl ? getAuthUrl(rawVerificationUri) : rawVerificationUri;
|
|
64
110
|
}
|
|
65
|
-
const { device_code, user_code, expires_in, interval } = deviceCodeResp.result;
|
|
66
|
-
// 暂时不消费 verification_uri,由客户端控制跳转
|
|
67
|
-
const defaultVerificationUri = `${web_auth_1.CLI_AUTH_BASE_URL}#/cli-auth`;
|
|
68
|
-
// from=cli 后续考虑改用 client_id,因为 toolbox 作为公共依赖,可能被不同 client 所引用
|
|
69
|
-
const rawVerificationUri = `${defaultVerificationUri}?user_code=${user_code}&from=cli&flow=device`;
|
|
70
|
-
const verification_uri = getAuthUrl ? getAuthUrl(rawVerificationUri) : rawVerificationUri;
|
|
71
111
|
if (!silent) {
|
|
72
|
-
// 打印授权信息,引导用户操作
|
|
73
112
|
console.log('\n\n若链接未自动打开,请手动复制至浏览器,或尝试其他登录方式:');
|
|
74
113
|
console.log(`\n${verification_uri}`);
|
|
75
114
|
console.log(`\n用户码: ${user_code}\n`);
|
|
@@ -79,21 +118,25 @@ function getAuthTokenByDeviceFlow(options = {}) {
|
|
|
79
118
|
if (onDeviceCode) {
|
|
80
119
|
onDeviceCode({ user_code, verification_uri, device_code, expires_in });
|
|
81
120
|
}
|
|
121
|
+
// ---- 轮询 token ----
|
|
82
122
|
let pollInterval = interval;
|
|
83
123
|
const deadline = Date.now() + expires_in * 1000;
|
|
84
124
|
while (Date.now() < deadline) {
|
|
85
125
|
yield sleep(pollInterval * 1000);
|
|
86
|
-
let
|
|
126
|
+
let tokenData;
|
|
127
|
+
let reqId;
|
|
87
128
|
try {
|
|
88
|
-
|
|
129
|
+
const tokenRaw = yield fetchPollToken({ device_code, interval: pollInterval, client_id, endpoint });
|
|
130
|
+
({ data: tokenData, reqId } = unwrapTokenResponse(tokenRaw, !!custom));
|
|
89
131
|
}
|
|
90
132
|
catch (e) {
|
|
133
|
+
if (e instanceof error_1.CloudBaseError)
|
|
134
|
+
throw e;
|
|
91
135
|
throw new error_1.CloudBaseError('Device Flow 轮询请求失败', { original: e });
|
|
92
136
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const { error, error_description } = result;
|
|
137
|
+
// 服务端返回业务错误(携带 error 字段)
|
|
138
|
+
if (tokenData && 'error' in tokenData) {
|
|
139
|
+
const { error, error_description } = tokenData;
|
|
97
140
|
if (error === POLL_ERROR_CODES.AUTHORIZATION_PENDING) {
|
|
98
141
|
continue;
|
|
99
142
|
}
|
|
@@ -109,17 +152,14 @@ function getAuthTokenByDeviceFlow(options = {}) {
|
|
|
109
152
|
}
|
|
110
153
|
throw new error_1.CloudBaseError(error_description || `Device Flow 授权失败,错误: ${error}`, {
|
|
111
154
|
code: error,
|
|
112
|
-
requestId:
|
|
155
|
+
requestId: reqId,
|
|
113
156
|
});
|
|
114
157
|
}
|
|
115
|
-
//
|
|
116
|
-
if (
|
|
117
|
-
return
|
|
158
|
+
// 成功拿到 Credential
|
|
159
|
+
if (tokenData) {
|
|
160
|
+
return tokenData;
|
|
118
161
|
}
|
|
119
|
-
throw new error_1.CloudBaseError(
|
|
120
|
-
code,
|
|
121
|
-
requestId: resp.reqId,
|
|
122
|
-
});
|
|
162
|
+
throw new error_1.CloudBaseError('Device Flow 授权失败:返回数据异常', { requestId: reqId });
|
|
123
163
|
}
|
|
124
164
|
throw new error_1.CloudBaseError('授权超时,用户未在有效期内完成授权,请重试');
|
|
125
165
|
});
|