@cloudbase/toolbox 0.7.20 → 0.7.21-beta.1
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/common.d.ts +6 -0
- package/lib/auth/common.js +14 -5
- package/lib/auth/credential.js +53 -1
- package/lib/auth/index.d.ts +11 -1
- package/lib/auth/index.js +65 -22
- package/lib/auth/oauth.d.ts +25 -0
- package/lib/auth/oauth.js +91 -41
- package/lib/auth/web-auth.d.ts +2 -0
- package/lib/auth/web-auth.js +3 -2
- package/lib/types.d.ts +97 -0
- package/lib/web/web.d.ts +3 -1
- package/lib/web/web.js +4 -0
- package/package.json +1 -1
package/lib/auth/common.d.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
+
import { LocalStore } from '../localstore';
|
|
1
2
|
import { Credential, WebAuthCredential, RequestConfig } from '../types';
|
|
3
|
+
export declare const DEFAULT_CLIENT_ID = "cloudbase-cli";
|
|
4
|
+
export declare const configStore: LocalStore<{
|
|
5
|
+
customOAuthEndpoint?: string;
|
|
6
|
+
client_id?: string;
|
|
7
|
+
}>;
|
|
2
8
|
export declare function checkAuth(credential: Credential, options?: RequestConfig): Promise<any>;
|
|
3
9
|
export declare function resolveCredential(data: Partial<Credential> & Partial<WebAuthCredential>): Credential;
|
|
4
10
|
export declare function resolveWebCredential(credential: Credential): WebAuthCredential;
|
package/lib/auth/common.js
CHANGED
|
@@ -9,9 +9,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.resolveWebCredential = exports.resolveCredential = exports.checkAuth = void 0;
|
|
12
|
+
exports.resolveWebCredential = exports.resolveCredential = exports.checkAuth = exports.configStore = exports.DEFAULT_CLIENT_ID = void 0;
|
|
13
13
|
const cloud_api_1 = require("@cloudbase/cloud-api");
|
|
14
14
|
const config_1 = require("../config");
|
|
15
|
+
const localstore_1 = require("../localstore");
|
|
16
|
+
exports.DEFAULT_CLIENT_ID = 'cloudbase-cli';
|
|
17
|
+
exports.configStore = new localstore_1.LocalStore({}, 'config');
|
|
15
18
|
// 调用 CheckTcbService 接口,检查密钥是否有效
|
|
16
19
|
// 相比 DescribeEnvs,该接口不会返回环境列表,仅验证登录态,性能更好
|
|
17
20
|
function checkAuth(credential, options = {}) {
|
|
@@ -39,7 +42,7 @@ function checkAuth(credential, options = {}) {
|
|
|
39
42
|
exports.checkAuth = checkAuth;
|
|
40
43
|
// 兼容解析旧的登录态
|
|
41
44
|
function resolveCredential(data) {
|
|
42
|
-
let { secretId, secretKey, token, accessTokenExpired, tmpSecretId, tmpSecretKey, tmpToken, tmpExpired, expired, authTime, refreshToken, uin, hash, envId } = data;
|
|
45
|
+
let { secretId, secretKey, token, accessTokenExpired, tmpSecretId, tmpSecretKey, tmpToken, tmpExpired, expired, authTime, refreshToken, uin, hash, envId, envIds, envList, envBillingInfoList } = data;
|
|
43
46
|
// 兼容旧的登录态信息
|
|
44
47
|
token = token || tmpToken;
|
|
45
48
|
secretId = secretId || tmpSecretId;
|
|
@@ -55,12 +58,15 @@ function resolveCredential(data) {
|
|
|
55
58
|
refreshToken,
|
|
56
59
|
uin,
|
|
57
60
|
hash,
|
|
58
|
-
envId
|
|
61
|
+
envId,
|
|
62
|
+
envIds,
|
|
63
|
+
envList,
|
|
64
|
+
envBillingInfoList
|
|
59
65
|
};
|
|
60
66
|
}
|
|
61
67
|
exports.resolveCredential = resolveCredential;
|
|
62
68
|
function resolveWebCredential(credential) {
|
|
63
|
-
const { secretId, secretKey, token, accessTokenExpired, expired, authTime, refreshToken, uin, hash, envId } = credential;
|
|
69
|
+
const { secretId, secretKey, token, accessTokenExpired, expired, authTime, refreshToken, uin, hash, envId, envIds, envList, envBillingInfoList } = credential;
|
|
64
70
|
const webCredential = {
|
|
65
71
|
tmpSecretId: secretId,
|
|
66
72
|
tmpSecretKey: secretKey,
|
|
@@ -71,7 +77,10 @@ function resolveWebCredential(credential) {
|
|
|
71
77
|
refreshToken,
|
|
72
78
|
uin,
|
|
73
79
|
hash,
|
|
74
|
-
envId
|
|
80
|
+
envId,
|
|
81
|
+
envIds,
|
|
82
|
+
envList,
|
|
83
|
+
envBillingInfoList
|
|
75
84
|
};
|
|
76
85
|
return webCredential;
|
|
77
86
|
}
|
package/lib/auth/credential.js
CHANGED
|
@@ -8,6 +8,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
12
|
+
var t = {};
|
|
13
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
14
|
+
t[p] = s[p];
|
|
15
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
16
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
17
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
18
|
+
t[p[i]] = s[p[i]];
|
|
19
|
+
}
|
|
20
|
+
return t;
|
|
21
|
+
};
|
|
11
22
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
23
|
exports.getCredentialWithoutCheck = exports.checkAndGetCredential = exports.isCamRefused = exports.refreshTmpToken = exports.getCredentialData = exports.ENV_NAME = exports.authStore = void 0;
|
|
13
24
|
const cloud_api_1 = require("@cloudbase/cloud-api");
|
|
@@ -67,12 +78,53 @@ function refreshTmpToken(metaData) {
|
|
|
67
78
|
const mac = yield (0, system_1.getMacAddress)();
|
|
68
79
|
const hash = (0, coding_1.md5Encoding)(mac);
|
|
69
80
|
metaData.hash = hash;
|
|
81
|
+
// 读取自定义 OAuth 配置
|
|
82
|
+
const customOAuthEndpoint = yield common_1.configStore.get('customOAuthEndpoint');
|
|
83
|
+
if (customOAuthEndpoint) {
|
|
84
|
+
// ===== 自定义模式 =====
|
|
85
|
+
const clientId = (yield common_1.configStore.get('client_id')) || common_1.DEFAULT_CLIENT_ID;
|
|
86
|
+
const body = {
|
|
87
|
+
grant_type: metaData.isLogout ? 'revoke_token' : 'refresh_token',
|
|
88
|
+
refresh_token: metaData.refreshToken,
|
|
89
|
+
client_id: clientId,
|
|
90
|
+
};
|
|
91
|
+
const res = yield async_1.AsyncMerge.merge(() => (0, cloud_api_1.fetch)(`${customOAuthEndpoint}/token`, {
|
|
92
|
+
method: 'POST',
|
|
93
|
+
timeout: 15000,
|
|
94
|
+
body: JSON.stringify(body),
|
|
95
|
+
headers: { 'Content-Type': 'application/json' }
|
|
96
|
+
}, (0, system_1.getProxy)()), 'refreshToken', {
|
|
97
|
+
maxAge: 5000
|
|
98
|
+
});
|
|
99
|
+
// 自定义模式错误响应:{ error, error_description }
|
|
100
|
+
if (res.error) {
|
|
101
|
+
throw new error_1.CloudBaseError(res.error_description || res.error, {
|
|
102
|
+
action: metaData.isLogout ? 'RevokeToken' : 'RefreshAccessToken',
|
|
103
|
+
code: res.error,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
// 登出(revoke_token)成功,无需返回凭证
|
|
107
|
+
if (metaData.isLogout) {
|
|
108
|
+
return res;
|
|
109
|
+
}
|
|
110
|
+
// 刷新 token 成功响应直接是 WebAuthCredential(无 code/data 包装)
|
|
111
|
+
if (!res || !res.tmpSecretId) {
|
|
112
|
+
throw new error_1.CloudBaseError('Token 刷新失败,可能是网络访问异常,请尝试重试');
|
|
113
|
+
}
|
|
114
|
+
if (metaData.envId) {
|
|
115
|
+
res.envId = metaData.envId;
|
|
116
|
+
}
|
|
117
|
+
return res;
|
|
118
|
+
}
|
|
119
|
+
// ===== 标准模式 =====
|
|
70
120
|
const credential = (0, common_1.resolveWebCredential)(metaData);
|
|
121
|
+
// 发送请求时不带 envIds/envList/envBillingInfoList
|
|
122
|
+
const { envIds: _a, envList: _b, envBillingInfoList: _c } = credential, requestBody = __rest(credential, ["envIds", "envList", "envBillingInfoList"]);
|
|
71
123
|
const res = yield async_1.AsyncMerge.merge(() => (0, cloud_api_1.fetch)(refreshTokenUrl, {
|
|
72
124
|
method: 'POST',
|
|
73
125
|
// 超时时间:15S
|
|
74
126
|
timeout: 15000,
|
|
75
|
-
body: JSON.stringify(
|
|
127
|
+
body: JSON.stringify(requestBody),
|
|
76
128
|
headers: { 'Content-Type': 'application/json' }
|
|
77
129
|
}, (0, system_1.getProxy)()), 'refreshToken', {
|
|
78
130
|
maxAge: 5000
|
package/lib/auth/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DeviceFlowOptions } from './oauth';
|
|
1
|
+
import { DeviceFlowOptions, DeviceCodeInfo, AuthTokenByDeviceCodeOptions } from './oauth';
|
|
2
2
|
import { Credential, RequestConfig } from '../types';
|
|
3
3
|
export * from './common';
|
|
4
4
|
export * from './credential';
|
|
@@ -45,6 +45,8 @@ export interface WebAuthOptions {
|
|
|
45
45
|
onDeviceCode?: DeviceFlowOptions['onDeviceCode'];
|
|
46
46
|
/** 自定义 OAuth API 请求的 endpoint */
|
|
47
47
|
getOAuthEndpoint?: DeviceFlowOptions['getOAuthEndpoint'];
|
|
48
|
+
/** 返回即将打开的授权链接 */
|
|
49
|
+
onAuthUrl?: (url: string) => void;
|
|
48
50
|
/** 静默模式,不打印任何日志 */
|
|
49
51
|
silent?: boolean;
|
|
50
52
|
/**
|
|
@@ -70,6 +72,10 @@ export interface LoginByApiSecretOptions {
|
|
|
70
72
|
*/
|
|
71
73
|
additionalCredentialFields?: Omit<Credential, 'secretId' | 'secretKey' | 'token'>;
|
|
72
74
|
}
|
|
75
|
+
export interface StartDeviceFlowOptions extends DeviceFlowOptions {
|
|
76
|
+
noBrowser?: boolean;
|
|
77
|
+
}
|
|
78
|
+
export type CompleteDeviceFlowOptions = AuthTokenByDeviceCodeOptions;
|
|
73
79
|
export declare class AuthSupervisor {
|
|
74
80
|
static instance: AuthSupervisor;
|
|
75
81
|
/**
|
|
@@ -92,6 +98,8 @@ export declare class AuthSupervisor {
|
|
|
92
98
|
* @returns credential
|
|
93
99
|
*/
|
|
94
100
|
loginByWebAuth(options?: WebAuthOptions): Promise<Credential>;
|
|
101
|
+
startDeviceFlow(options?: StartDeviceFlowOptions): Promise<DeviceCodeInfo>;
|
|
102
|
+
completeDeviceFlow(options: CompleteDeviceFlowOptions): Promise<Credential>;
|
|
95
103
|
/**
|
|
96
104
|
* 通过 API Secret 登录,支持临时秘钥
|
|
97
105
|
* @param secretId
|
|
@@ -102,6 +110,8 @@ export declare class AuthSupervisor {
|
|
|
102
110
|
*/
|
|
103
111
|
loginByApiSecret(secretId?: string, secretKey?: string, token?: string, opts?: LoginByApiSecretOptions): Promise<Credential>;
|
|
104
112
|
logout(): Promise<void>;
|
|
113
|
+
private finalizeWebAuthCredential;
|
|
114
|
+
private updateCredentialCache;
|
|
105
115
|
private isCacheExpire;
|
|
106
116
|
}
|
|
107
117
|
/**
|
package/lib/auth/index.js
CHANGED
|
@@ -22,6 +22,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
22
22
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
23
23
|
});
|
|
24
24
|
};
|
|
25
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
26
|
+
var t = {};
|
|
27
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
28
|
+
t[p] = s[p];
|
|
29
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
30
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
31
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
32
|
+
t[p[i]] = s[p[i]];
|
|
33
|
+
}
|
|
34
|
+
return t;
|
|
35
|
+
};
|
|
25
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
37
|
exports.AuthSupevisor = exports.AuthSupervisor = void 0;
|
|
27
38
|
const web_auth_1 = require("./web-auth");
|
|
@@ -29,6 +40,7 @@ const oauth_1 = require("./oauth");
|
|
|
29
40
|
const common_1 = require("./common");
|
|
30
41
|
const credential_1 = require("./credential");
|
|
31
42
|
const error_1 = require("../error");
|
|
43
|
+
const web_1 = require("../web");
|
|
32
44
|
__exportStar(require("./common"), exports);
|
|
33
45
|
__exportStar(require("./credential"), exports);
|
|
34
46
|
__exportStar(require("./web-auth"), exports);
|
|
@@ -82,7 +94,7 @@ class AuthSupervisor {
|
|
|
82
94
|
*/
|
|
83
95
|
loginByWebAuth(options = {}) {
|
|
84
96
|
return __awaiter(this, void 0, void 0, function* () {
|
|
85
|
-
const { getAuthUrl, throwError, noBrowser, callbackTimeout, flow = 'device', client_id, onDeviceCode, getOAuthEndpoint, silent, custom } = options;
|
|
97
|
+
const { getAuthUrl, throwError, noBrowser, callbackTimeout, flow = 'device', client_id, onDeviceCode, getOAuthEndpoint, onAuthUrl, silent, custom } = options;
|
|
86
98
|
if (this.cacheCredential && this.needCache && !this.isCacheExpire()) {
|
|
87
99
|
return this.cacheCredential;
|
|
88
100
|
}
|
|
@@ -96,34 +108,36 @@ class AuthSupervisor {
|
|
|
96
108
|
getAuthUrl,
|
|
97
109
|
noBrowser,
|
|
98
110
|
callbackTimeout,
|
|
99
|
-
silent
|
|
111
|
+
silent,
|
|
112
|
+
onAuthUrl
|
|
100
113
|
});
|
|
101
114
|
}
|
|
102
115
|
else {
|
|
103
116
|
credential = yield (0, oauth_1.getAuthTokenByDeviceFlow)({ client_id, onDeviceCode, getOAuthEndpoint, getAuthUrl, silent, custom });
|
|
104
117
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
118
|
+
return this.finalizeWebAuthCredential(credential, throwError);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
startDeviceFlow(options = {}) {
|
|
122
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
123
|
+
const { noBrowser } = options, deviceFlowOptions = __rest(options, ["noBrowser"]);
|
|
124
|
+
const deviceCodeInfo = yield (0, oauth_1.getDeviceCodeByDeviceFlow)(deviceFlowOptions);
|
|
125
|
+
if (!noBrowser) {
|
|
126
|
+
yield (0, web_1.openUrl)(deviceCodeInfo.verification_uri);
|
|
114
127
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
this.cacheExpiredTime = accessTokenExpired
|
|
123
|
-
? Number(accessTokenExpired)
|
|
124
|
-
: Date.now() + 3600 * 1000;
|
|
128
|
+
return deviceCodeInfo;
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
completeDeviceFlow(options) {
|
|
132
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
133
|
+
if (this.cacheCredential && this.needCache && !this.isCacheExpire()) {
|
|
134
|
+
return this.cacheCredential;
|
|
125
135
|
}
|
|
126
|
-
|
|
136
|
+
let credential = yield (0, credential_1.checkAndGetCredential)(this.requestConfig);
|
|
137
|
+
if (credential)
|
|
138
|
+
return credential;
|
|
139
|
+
credential = yield (0, oauth_1.getAuthTokenByDeviceCode)(options);
|
|
140
|
+
return this.finalizeWebAuthCredential(credential);
|
|
127
141
|
});
|
|
128
142
|
}
|
|
129
143
|
/**
|
|
@@ -200,6 +214,35 @@ class AuthSupervisor {
|
|
|
200
214
|
}
|
|
201
215
|
});
|
|
202
216
|
}
|
|
217
|
+
finalizeWebAuthCredential(credential, throwError) {
|
|
218
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
219
|
+
// eslint-disable-next-line no-param-reassign
|
|
220
|
+
credential = (0, common_1.resolveCredential)(credential);
|
|
221
|
+
try {
|
|
222
|
+
yield (0, common_1.checkAuth)(credential, this.requestConfig);
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
if (throwError || this.throwError) {
|
|
226
|
+
throw error;
|
|
227
|
+
}
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
const webCredential = (0, common_1.resolveWebCredential)(credential);
|
|
231
|
+
yield credential_1.authStore.set('credential', webCredential);
|
|
232
|
+
this.updateCredentialCache(credential);
|
|
233
|
+
return credential;
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
updateCredentialCache(credential) {
|
|
237
|
+
if (!this.needCache || !credential) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
this.cacheCredential = credential;
|
|
241
|
+
const { accessTokenExpired } = credential;
|
|
242
|
+
this.cacheExpiredTime = accessTokenExpired
|
|
243
|
+
? Number(accessTokenExpired)
|
|
244
|
+
: Date.now() + 3600 * 1000;
|
|
245
|
+
}
|
|
203
246
|
isCacheExpire() {
|
|
204
247
|
const now = Date.now();
|
|
205
248
|
this.cacheExpiredTime = this.cacheExpiredTime || 0;
|
package/lib/auth/oauth.d.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { Credential } from "../types";
|
|
2
|
+
export interface DeviceCodeInfo {
|
|
3
|
+
device_code: string;
|
|
4
|
+
user_code: string;
|
|
5
|
+
verification_uri: string;
|
|
6
|
+
expires_in: number;
|
|
7
|
+
interval: number;
|
|
8
|
+
}
|
|
2
9
|
export interface DeviceFlowOptions {
|
|
3
10
|
client_id?: string;
|
|
4
11
|
onDeviceCode?: (data: {
|
|
@@ -20,4 +27,22 @@ export interface DeviceFlowOptions {
|
|
|
20
27
|
*/
|
|
21
28
|
custom?: boolean;
|
|
22
29
|
}
|
|
30
|
+
export interface AuthTokenByDeviceCodeOptions {
|
|
31
|
+
device_code: string;
|
|
32
|
+
/** 初始轮询间隔 s,默认 5 */
|
|
33
|
+
interval?: number;
|
|
34
|
+
client_id?: string;
|
|
35
|
+
/** 自定义 OAuth API 请求的 endpoint */
|
|
36
|
+
getOAuthEndpoint?: DeviceFlowOptions['getOAuthEndpoint'];
|
|
37
|
+
/**
|
|
38
|
+
* 自定义授权流程模式
|
|
39
|
+
* 为 true 时,token 接口的返回值没有外层 { code, result, reqId } 包装,
|
|
40
|
+
* 直接就是 result 本体。
|
|
41
|
+
*/
|
|
42
|
+
custom?: DeviceFlowOptions['custom'];
|
|
43
|
+
/** 最长等待时长 ms,默认 30000 */
|
|
44
|
+
timeout?: number;
|
|
45
|
+
}
|
|
46
|
+
export declare function getDeviceCodeByDeviceFlow(options?: DeviceFlowOptions): Promise<DeviceCodeInfo>;
|
|
47
|
+
export declare function getAuthTokenByDeviceCode(options: AuthTokenByDeviceCodeOptions): Promise<Credential>;
|
|
23
48
|
export declare function getAuthTokenByDeviceFlow(options?: DeviceFlowOptions): Promise<Credential>;
|
package/lib/auth/oauth.js
CHANGED
|
@@ -9,16 +9,31 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.getAuthTokenByDeviceFlow = void 0;
|
|
12
|
+
exports.getAuthTokenByDeviceFlow = exports.getAuthTokenByDeviceCode = exports.getDeviceCodeByDeviceFlow = void 0;
|
|
13
13
|
const net_1 = require("../net");
|
|
14
14
|
const web_1 = require("../web");
|
|
15
15
|
const error_1 = require("../error");
|
|
16
16
|
const coding_1 = require("../coding");
|
|
17
17
|
const system_1 = require("../system");
|
|
18
18
|
const web_auth_1 = require("./web-auth");
|
|
19
|
+
const common_1 = require("./common");
|
|
19
20
|
/** 默认国内站 应用侧可通过 getOAuthEndpoint 覆写 */
|
|
20
|
-
const
|
|
21
|
-
|
|
21
|
+
const DEFAULT_OAUTH_ENDPOINT = process.env.TCB_OAUTH_ENDPOINT || 'https://tcb-api.cloud.tencent.com/qcloud-tcb/v1/oauth';
|
|
22
|
+
/**
|
|
23
|
+
* 解析 OAuth Endpoint,优先级:getOAuthEndpoint > customOAuthEndpoint > default
|
|
24
|
+
*/
|
|
25
|
+
function resolveOAuthEndpoint(getOAuthEndpoint) {
|
|
26
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
27
|
+
if (getOAuthEndpoint) {
|
|
28
|
+
return getOAuthEndpoint(DEFAULT_OAUTH_ENDPOINT);
|
|
29
|
+
}
|
|
30
|
+
const customOAuthEndpoint = yield common_1.configStore.get('customOAuthEndpoint');
|
|
31
|
+
if (customOAuthEndpoint) {
|
|
32
|
+
return customOAuthEndpoint;
|
|
33
|
+
}
|
|
34
|
+
return DEFAULT_OAUTH_ENDPOINT;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
22
37
|
const POLL_ERROR_CODES = {
|
|
23
38
|
AUTHORIZATION_PENDING: 'authorization_pending',
|
|
24
39
|
SLOW_DOWN: 'slow_down',
|
|
@@ -27,12 +42,12 @@ const POLL_ERROR_CODES = {
|
|
|
27
42
|
};
|
|
28
43
|
const SUCCESS_CODE = 'NORMAL';
|
|
29
44
|
function fetchDeviceCode(options = {}) {
|
|
30
|
-
const { client_id = DEFAULT_CLIENT_ID, endpoint =
|
|
45
|
+
const { client_id = common_1.DEFAULT_CLIENT_ID, endpoint = DEFAULT_OAUTH_ENDPOINT } = options;
|
|
31
46
|
return (0, net_1.postFetch)(`${endpoint}/device/code`, { client_id });
|
|
32
47
|
}
|
|
33
48
|
function fetchPollToken(options) {
|
|
34
49
|
return __awaiter(this, void 0, void 0, function* () {
|
|
35
|
-
const { device_code, client_id = DEFAULT_CLIENT_ID, endpoint =
|
|
50
|
+
const { device_code, client_id = common_1.DEFAULT_CLIENT_ID, endpoint = DEFAULT_OAUTH_ENDPOINT } = options;
|
|
36
51
|
const mac = yield (0, system_1.getMacAddress)();
|
|
37
52
|
return (0, net_1.postFetch)(`${endpoint}/token`, {
|
|
38
53
|
device_code,
|
|
@@ -51,6 +66,17 @@ function sleep(ms) {
|
|
|
51
66
|
setTimeout(resolve, ms);
|
|
52
67
|
});
|
|
53
68
|
}
|
|
69
|
+
function buildVerificationUri(deviceCodeData, options) {
|
|
70
|
+
const { custom, getAuthUrl } = options;
|
|
71
|
+
if (custom) {
|
|
72
|
+
const rawUri = deviceCodeData.verification_uri || '';
|
|
73
|
+
return getAuthUrl ? getAuthUrl(rawUri) : rawUri;
|
|
74
|
+
}
|
|
75
|
+
const defaultVerificationUri = `${web_auth_1.CLI_AUTH_BASE_URL}#/cli-auth`;
|
|
76
|
+
// from=cli 后续考虑改用 client_id,因为 toolbox 作为公共依赖,可能被不同 client 所引用
|
|
77
|
+
const rawVerificationUri = `${defaultVerificationUri}?user_code=${deviceCodeData.user_code}&flow=device&from=cli`;
|
|
78
|
+
return getAuthUrl ? getAuthUrl(rawVerificationUri) : rawVerificationUri;
|
|
79
|
+
}
|
|
54
80
|
/**
|
|
55
81
|
* 将接口原始返回值归一化为裸数据。
|
|
56
82
|
* - 标准模式:从 { code, result, reqId } 中解包,校验 code
|
|
@@ -86,43 +112,24 @@ function unwrapTokenResponse(raw, custom) {
|
|
|
86
112
|
}
|
|
87
113
|
return { data: resp.result, reqId: resp.reqId };
|
|
88
114
|
}
|
|
89
|
-
function
|
|
115
|
+
function getDeviceCodeByDeviceFlow(options = {}) {
|
|
90
116
|
return __awaiter(this, void 0, void 0, function* () {
|
|
91
|
-
const { client_id,
|
|
92
|
-
const endpoint =
|
|
93
|
-
// ---- 获取 device code ----
|
|
117
|
+
const { client_id, getOAuthEndpoint, getAuthUrl, custom } = options;
|
|
118
|
+
const endpoint = yield resolveOAuthEndpoint(getOAuthEndpoint);
|
|
94
119
|
const deviceCodeRaw = yield fetchDeviceCode({ client_id, endpoint });
|
|
95
120
|
const { data: deviceCodeData } = unwrapDeviceCodeResponse(deviceCodeRaw, !!custom);
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
|
|
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;
|
|
110
|
-
}
|
|
111
|
-
if (!silent) {
|
|
112
|
-
console.log('\n\n若链接未自动打开,请手动复制至浏览器,或尝试其他登录方式:');
|
|
113
|
-
console.log(`\n${verification_uri}`);
|
|
114
|
-
console.log(`\n用户码: ${user_code}\n`);
|
|
115
|
-
}
|
|
116
|
-
// 尝试自动打开授权页面
|
|
117
|
-
yield (0, web_1.openUrl)(verification_uri);
|
|
118
|
-
if (onDeviceCode) {
|
|
119
|
-
onDeviceCode({ user_code, verification_uri, device_code, expires_in });
|
|
120
|
-
}
|
|
121
|
-
// ---- 轮询 token ----
|
|
121
|
+
const verification_uri = buildVerificationUri(deviceCodeData, { custom, getAuthUrl });
|
|
122
|
+
return Object.assign(Object.assign({}, deviceCodeData), { verification_uri });
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
exports.getDeviceCodeByDeviceFlow = getDeviceCodeByDeviceFlow;
|
|
126
|
+
function getAuthTokenByDeviceCode(options) {
|
|
127
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
128
|
+
const { device_code, interval = 5, client_id, getOAuthEndpoint, custom, timeout = 30000 } = options;
|
|
129
|
+
const endpoint = yield resolveOAuthEndpoint(getOAuthEndpoint);
|
|
122
130
|
let pollInterval = interval;
|
|
123
|
-
const deadline = Date.now() +
|
|
124
|
-
while (Date.now() < deadline) {
|
|
125
|
-
yield sleep(pollInterval * 1000);
|
|
131
|
+
const deadline = timeout > 0 ? Date.now() + timeout : 0;
|
|
132
|
+
while (!deadline || Date.now() < deadline) {
|
|
126
133
|
let tokenData;
|
|
127
134
|
let reqId;
|
|
128
135
|
try {
|
|
@@ -134,14 +141,23 @@ function getAuthTokenByDeviceFlow(options = {}) {
|
|
|
134
141
|
throw e;
|
|
135
142
|
throw new error_1.CloudBaseError('Device Flow 轮询请求失败', { original: e });
|
|
136
143
|
}
|
|
137
|
-
// 服务端返回业务错误(携带 error 字段)
|
|
138
144
|
if (tokenData && 'error' in tokenData) {
|
|
139
145
|
const { error, error_description } = tokenData;
|
|
140
146
|
if (error === POLL_ERROR_CODES.AUTHORIZATION_PENDING) {
|
|
147
|
+
const nextWaitMs = pollInterval * 1000;
|
|
148
|
+
if (deadline && Date.now() + nextWaitMs >= deadline) {
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
yield sleep(nextWaitMs);
|
|
141
152
|
continue;
|
|
142
153
|
}
|
|
143
154
|
if (error === POLL_ERROR_CODES.SLOW_DOWN) {
|
|
144
155
|
pollInterval += 5;
|
|
156
|
+
const nextWaitMs = pollInterval * 1000;
|
|
157
|
+
if (deadline && Date.now() + nextWaitMs >= deadline) {
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
yield sleep(nextWaitMs);
|
|
145
161
|
continue;
|
|
146
162
|
}
|
|
147
163
|
if (error === POLL_ERROR_CODES.EXPIRED_TOKEN) {
|
|
@@ -155,13 +171,47 @@ function getAuthTokenByDeviceFlow(options = {}) {
|
|
|
155
171
|
requestId: reqId,
|
|
156
172
|
});
|
|
157
173
|
}
|
|
158
|
-
// 成功拿到 Credential
|
|
159
174
|
if (tokenData) {
|
|
160
175
|
return tokenData;
|
|
161
176
|
}
|
|
162
177
|
throw new error_1.CloudBaseError('Device Flow 授权失败:返回数据异常', { requestId: reqId });
|
|
163
178
|
}
|
|
164
|
-
throw new error_1.CloudBaseError('
|
|
179
|
+
throw new error_1.CloudBaseError('授权尚未完成,请确认已完成授权后重试', {
|
|
180
|
+
code: POLL_ERROR_CODES.AUTHORIZATION_PENDING,
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
exports.getAuthTokenByDeviceCode = getAuthTokenByDeviceCode;
|
|
185
|
+
function getAuthTokenByDeviceFlow(options = {}) {
|
|
186
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
187
|
+
const { client_id, onDeviceCode, getOAuthEndpoint, getAuthUrl, silent, custom } = options;
|
|
188
|
+
const { device_code, user_code, verification_uri, expires_in, interval } = yield getDeviceCodeByDeviceFlow({ client_id, getOAuthEndpoint, getAuthUrl, custom });
|
|
189
|
+
if (!silent) {
|
|
190
|
+
console.log('\n\n若链接未自动打开,请手动复制至浏览器,或尝试其他登录方式:');
|
|
191
|
+
console.log(`\n${verification_uri}`);
|
|
192
|
+
console.log(`\n用户码: ${user_code}\n`);
|
|
193
|
+
}
|
|
194
|
+
// 尝试自动打开授权页面
|
|
195
|
+
yield (0, web_1.openUrl)(verification_uri);
|
|
196
|
+
if (onDeviceCode) {
|
|
197
|
+
onDeviceCode({ user_code, verification_uri, device_code, expires_in });
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
return yield getAuthTokenByDeviceCode({
|
|
201
|
+
device_code,
|
|
202
|
+
interval,
|
|
203
|
+
client_id,
|
|
204
|
+
getOAuthEndpoint,
|
|
205
|
+
custom,
|
|
206
|
+
timeout: expires_in * 1000
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
catch (e) {
|
|
210
|
+
if (e instanceof error_1.CloudBaseError && e.code === POLL_ERROR_CODES.AUTHORIZATION_PENDING) {
|
|
211
|
+
throw new error_1.CloudBaseError('授权超时,用户未在有效期内完成授权,请重试');
|
|
212
|
+
}
|
|
213
|
+
throw e;
|
|
214
|
+
}
|
|
165
215
|
});
|
|
166
216
|
}
|
|
167
217
|
exports.getAuthTokenByDeviceFlow = getAuthTokenByDeviceFlow;
|
package/lib/auth/web-auth.d.ts
CHANGED
package/lib/auth/web-auth.js
CHANGED
|
@@ -18,7 +18,7 @@ exports.CLI_AUTH_BASE_URL = 'https://tcb.cloud.tencent.com/dev';
|
|
|
18
18
|
// 打开云开发控制台,获取授权
|
|
19
19
|
function getAuthTokenFromWeb(options = {}) {
|
|
20
20
|
return __awaiter(this, void 0, void 0, function* () {
|
|
21
|
-
const { getAuthUrl, noBrowser, callbackTimeout, silent } = options;
|
|
21
|
+
const { getAuthUrl, noBrowser, callbackTimeout, silent, onAuthUrl } = options;
|
|
22
22
|
const mac = yield (0, system_1.getMacAddress)();
|
|
23
23
|
const os = (0, system_1.getOSInfo)();
|
|
24
24
|
const macHash = (0, coding_1.md5Encoding)(mac);
|
|
@@ -45,7 +45,8 @@ function getAuthTokenFromWeb(options = {}) {
|
|
|
45
45
|
}, 'login', {
|
|
46
46
|
noBrowser,
|
|
47
47
|
callbackTimeout,
|
|
48
|
-
silent
|
|
48
|
+
silent,
|
|
49
|
+
onUrl: onAuthUrl
|
|
49
50
|
});
|
|
50
51
|
const credential = (0, common_1.resolveCredential)(query);
|
|
51
52
|
return credential;
|
package/lib/types.d.ts
CHANGED
|
@@ -1,3 +1,88 @@
|
|
|
1
|
+
export interface EnvInfo {
|
|
2
|
+
EnvId: string;
|
|
3
|
+
PackageType?: string;
|
|
4
|
+
EnvType?: string;
|
|
5
|
+
IsDauPackage?: boolean;
|
|
6
|
+
Source?: string;
|
|
7
|
+
Alias?: string;
|
|
8
|
+
Status?: string;
|
|
9
|
+
PayMode?: string;
|
|
10
|
+
Tags?: Record<string, string>[];
|
|
11
|
+
PackageName?: string;
|
|
12
|
+
IsAutoDegrade?: boolean;
|
|
13
|
+
EnvChannel?: string;
|
|
14
|
+
Region?: string;
|
|
15
|
+
IsDefault?: boolean;
|
|
16
|
+
PackageId?: string;
|
|
17
|
+
CreateTime?: string;
|
|
18
|
+
UpdateTime?: string;
|
|
19
|
+
Databases?: {
|
|
20
|
+
InstanceId: string;
|
|
21
|
+
Region: string;
|
|
22
|
+
Status: string;
|
|
23
|
+
UpdateTime: string;
|
|
24
|
+
}[];
|
|
25
|
+
Storages?: {
|
|
26
|
+
Region: string;
|
|
27
|
+
Bucket: string;
|
|
28
|
+
CdnDomain: string;
|
|
29
|
+
AppId: string;
|
|
30
|
+
}[];
|
|
31
|
+
Functions?: {
|
|
32
|
+
Namespace: string;
|
|
33
|
+
Region: string;
|
|
34
|
+
}[];
|
|
35
|
+
LogServices?: {
|
|
36
|
+
LogsetName: string;
|
|
37
|
+
LogsetId: string;
|
|
38
|
+
TopicName: string;
|
|
39
|
+
TopicId: string;
|
|
40
|
+
Region: string;
|
|
41
|
+
}[];
|
|
42
|
+
StaticStorages?: {
|
|
43
|
+
Type: string;
|
|
44
|
+
Bucket: string;
|
|
45
|
+
Region: string;
|
|
46
|
+
Status: string;
|
|
47
|
+
}[];
|
|
48
|
+
CustomLogServices?: {
|
|
49
|
+
ClsTopicId: string;
|
|
50
|
+
ClsRegion: string;
|
|
51
|
+
ClsLogsetId: string;
|
|
52
|
+
CreateTime: string;
|
|
53
|
+
}[];
|
|
54
|
+
}
|
|
55
|
+
export interface OrderInfo {
|
|
56
|
+
TranId?: string;
|
|
57
|
+
PackageId?: string;
|
|
58
|
+
TranType?: string;
|
|
59
|
+
TranStatus?: string;
|
|
60
|
+
UpdateTime?: string;
|
|
61
|
+
CreateTime?: string;
|
|
62
|
+
PayMode?: string;
|
|
63
|
+
ExtensionId?: string;
|
|
64
|
+
ResourceReady?: string;
|
|
65
|
+
Flag?: string;
|
|
66
|
+
ReqBody?: string;
|
|
67
|
+
}
|
|
68
|
+
export interface EnvBillingInfoItem {
|
|
69
|
+
EnvId: string;
|
|
70
|
+
PackageId?: string;
|
|
71
|
+
PackageName?: string;
|
|
72
|
+
IsAutoRenew?: boolean;
|
|
73
|
+
Status?: string;
|
|
74
|
+
PayMode?: string;
|
|
75
|
+
IsolatedTime?: string;
|
|
76
|
+
ExpireTime?: string;
|
|
77
|
+
CreateTime?: string;
|
|
78
|
+
UpdateTime?: string;
|
|
79
|
+
FreeQuota?: string;
|
|
80
|
+
PaymentChannel?: string;
|
|
81
|
+
ExtPackageType?: string;
|
|
82
|
+
OrderInfo?: OrderInfo;
|
|
83
|
+
IsAlwaysFree?: boolean;
|
|
84
|
+
EnableOverrun?: boolean;
|
|
85
|
+
}
|
|
1
86
|
export interface Credential {
|
|
2
87
|
secretId: string;
|
|
3
88
|
secretKey: string;
|
|
@@ -9,6 +94,12 @@ export interface Credential {
|
|
|
9
94
|
uin?: string;
|
|
10
95
|
hash?: string;
|
|
11
96
|
envId?: string;
|
|
97
|
+
/** 该用户可用的环境 ID 列表(有序,与 envList 顺序一致) */
|
|
98
|
+
envIds?: string[];
|
|
99
|
+
/** 环境批量详情,结构同 DescribeEnvs 接口返回的 EnvList */
|
|
100
|
+
envList?: EnvInfo[];
|
|
101
|
+
/** 环境计费信息列表,结构同 DescribeBillingInfo 接口返回的 EnvBillingInfoList */
|
|
102
|
+
envBillingInfoList?: EnvBillingInfoItem[];
|
|
12
103
|
}
|
|
13
104
|
export interface WebAuthCredential {
|
|
14
105
|
tmpSecretId: string;
|
|
@@ -21,6 +112,12 @@ export interface WebAuthCredential {
|
|
|
21
112
|
uin?: string;
|
|
22
113
|
hash?: string;
|
|
23
114
|
envId?: string;
|
|
115
|
+
/** 该用户可用的环境 ID 列表(有序,与 envList 顺序一致) */
|
|
116
|
+
envIds?: string[];
|
|
117
|
+
/** 环境批量详情,结构同 DescribeEnvs 接口返回的 EnvList */
|
|
118
|
+
envList?: EnvInfo[];
|
|
119
|
+
/** 环境计费信息列表,结构同 DescribeBillingInfo 接口返回的 EnvBillingInfoList */
|
|
120
|
+
envBillingInfoList?: EnvBillingInfoItem[];
|
|
24
121
|
}
|
|
25
122
|
export interface RequestConfig {
|
|
26
123
|
proxy?: string;
|
package/lib/web/web.d.ts
CHANGED
|
@@ -6,8 +6,10 @@ export type CheckFn = (query: IQuery) => Promise<void>;
|
|
|
6
6
|
export interface GetDataFromWebOptions {
|
|
7
7
|
noBrowser?: boolean;
|
|
8
8
|
callbackTimeout?: number;
|
|
9
|
-
/**
|
|
9
|
+
/** 静默模式,不打印任何日志 */
|
|
10
10
|
silent?: boolean;
|
|
11
|
+
/** 返回即将打开的授权链接 */
|
|
12
|
+
onUrl?: (url: string) => void;
|
|
11
13
|
}
|
|
12
14
|
export declare function isTruthyFlag(value?: string): boolean;
|
|
13
15
|
export declare function openUrl(url: string): Promise<boolean>;
|
package/lib/web/web.js
CHANGED
|
@@ -124,10 +124,14 @@ function getDataFromWeb(getUrl, type, options = {}) {
|
|
|
124
124
|
const noBrowser = (_a = options.noBrowser) !== null && _a !== void 0 ? _a : isTruthyFlag(process.env.TCB_NO_BROWSER);
|
|
125
125
|
const callbackTimeout = (_b = options.callbackTimeout) !== null && _b !== void 0 ? _b : 180000;
|
|
126
126
|
const silent = (_c = options.silent) !== null && _c !== void 0 ? _c : false;
|
|
127
|
+
const onUrl = options.onUrl;
|
|
127
128
|
if (!Number.isFinite(callbackTimeout) || callbackTimeout <= 0) {
|
|
128
129
|
throw new error_1.CloudBaseError('callbackTimeout must be a positive number');
|
|
129
130
|
}
|
|
130
131
|
const url = getUrl(port);
|
|
132
|
+
if (onUrl) {
|
|
133
|
+
onUrl(url);
|
|
134
|
+
}
|
|
131
135
|
if (!silent) {
|
|
132
136
|
console.log('\n\n若链接未自动打开,请手动复制至浏览器,或尝试其他登录方式:');
|
|
133
137
|
console.log(`\n${url}\n`);
|