openyida 2026.7.13 → 2026.7.14-2
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/README.md +15 -22
- package/bin/yida.js +72 -220
- package/lib/agent-center/api.js +2 -2
- package/lib/aggregate-table/aggregate-table.js +2 -2
- package/lib/ai/ai.js +7 -12
- package/lib/app/canvas-compile.js +2 -2
- package/lib/app/create-app.js +5 -19
- package/lib/app/create-form.js +54 -76
- package/lib/app/create-page.js +4 -19
- package/lib/app/er.js +11 -8
- package/lib/app/export-app.js +2 -2
- package/lib/app/externalize-form.js +12 -10
- package/lib/app/get-schema.js +12 -10
- package/lib/app/import-app.js +6 -22
- package/lib/app/list-forms.js +3 -19
- package/lib/app/nav-group.js +6 -19
- package/lib/app/publish.js +60 -198
- package/lib/app/update-app.js +4 -18
- package/lib/app/update-form-config.js +20 -113
- package/lib/app-permission/app-permission.js +2 -2
- package/lib/auth/oauth-loopback.js +156 -0
- package/lib/auth/org.js +169 -322
- package/lib/auth/token-auth.js +346 -0
- package/lib/auth/token-store.js +174 -0
- package/lib/basic-info/basic-info.js +11 -12
- package/lib/bridge/bridge.js +29 -27
- package/lib/connector/api.js +4 -4
- package/lib/core/agent-capabilities.js +92 -8
- package/lib/core/batch.js +8 -8
- package/lib/core/check-data.js +2 -2
- package/lib/core/command-manifest.js +3 -3
- package/lib/core/doctor.js +19 -22
- package/lib/core/env-cmd.js +1 -3
- package/lib/core/env-manager.js +8 -121
- package/lib/core/env.js +30 -72
- package/lib/core/locales/ar.js +5 -5
- package/lib/core/locales/de.js +5 -5
- package/lib/core/locales/en.js +26 -26
- package/lib/core/locales/es.js +5 -5
- package/lib/core/locales/fr.js +5 -5
- package/lib/core/locales/hi.js +5 -5
- package/lib/core/locales/ja.js +14 -14
- package/lib/core/locales/ko.js +5 -5
- package/lib/core/locales/pt.js +5 -5
- package/lib/core/locales/vi.js +5 -5
- package/lib/core/locales/zh-HK.js +15 -15
- package/lib/core/locales/zh.js +26 -26
- package/lib/core/query-data.js +2 -2
- package/lib/core/task-center.js +2 -2
- package/lib/core/utils.js +156 -400
- package/lib/core/yida-client.js +57 -22
- package/lib/corp-efficiency/corp-efficiency.js +3 -3
- package/lib/corp-manager/api.js +2 -2
- package/lib/db/db-seq-fix.js +20 -25
- package/lib/flash-note/flash-to-prd.js +2 -2
- package/lib/i18n-management/i18n-management.js +2 -2
- package/lib/integration/integration-check.js +10 -7
- package/lib/integration/integration-create.js +11 -8
- package/lib/integration/integration-list.js +11 -8
- package/lib/mcp/server.js +5 -54
- package/lib/page-config/get-page-config.js +2 -2
- package/lib/page-config/save-share-config.js +2 -2
- package/lib/page-config/verify-short-url.js +2 -2
- package/lib/permission/get-permission.js +2 -2
- package/lib/permission/save-permission.js +2 -2
- package/lib/process/ai-form-setting.js +2 -2
- package/lib/process/create-process.js +2 -2
- package/lib/process/preview-process.js +2 -2
- package/lib/report/append.js +3 -3
- package/lib/report/index.js +3 -3
- package/package.json +1 -1
- package/scripts/check-syntax.js +6 -0
- package/scripts/e2e-real/full-runner.js +2 -10
- package/scripts/e2e-real/runner.js +0 -31
- package/scripts/eval/screenshot.js +27 -69
- package/scripts/nightly-smoke.js +16 -34
- package/scripts/postinstall.js +6 -2
- package/yida-skills/SKILL.md +10 -6
- package/yida-skills/references/development-rules.md +2 -2
- package/yida-skills/references/setup-and-env.md +10 -9
- package/yida-skills/skills/sls-log-workbench/SKILL.md +2 -2
- package/yida-skills/skills/sls-log-workbench/sls-query.js +27 -54
- package/yida-skills/skills/yida-flash-note-to-prd/SKILL.md +1 -1
- package/yida-skills/skills/yida-integration/SKILL.md +3 -3
- package/yida-skills/skills/yida-login/SKILL.md +56 -74
- package/yida-skills/skills/yida-logout/SKILL.md +13 -12
- package/yida-skills/skills/yida-process-rule/SKILL.md +1 -1
- package/yida-skills/skills/yida-publish-page/SKILL.md +1 -1
- package/yida-skills/skills/yida-report/references/report-api-guide.md +4 -20
- package/yida-skills/skills-index.json +4 -4
- package/lib/auth/auth.js +0 -313
- package/lib/auth/cdp-browser-login.js +0 -390
- package/lib/auth/codex-login.js +0 -71
- package/lib/auth/login.js +0 -550
- package/lib/auth/qr-login.js +0 -1575
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const http = require('http');
|
|
4
|
+
const https = require('https');
|
|
5
|
+
|
|
6
|
+
const {
|
|
7
|
+
DINGTALK_OAUTH_CLIENT_ID,
|
|
8
|
+
DINGTALK_LOGIN_ORIGIN,
|
|
9
|
+
DINGTALK_INTL_LOGIN_ORIGIN,
|
|
10
|
+
getCurrentEnvConfig,
|
|
11
|
+
normalizeBaseUrl,
|
|
12
|
+
} = require('../core/env-manager');
|
|
13
|
+
const { runDingtalkLoopback } = require('./oauth-loopback');
|
|
14
|
+
const {
|
|
15
|
+
clearTokenSession,
|
|
16
|
+
isAccessTokenUsable,
|
|
17
|
+
loadTokenSession,
|
|
18
|
+
maskToken,
|
|
19
|
+
saveTokenSession,
|
|
20
|
+
} = require('./token-store');
|
|
21
|
+
|
|
22
|
+
const DEFAULT_AUTH_PATH_PREFIX = '/openapi/cli/v1/auth';
|
|
23
|
+
|
|
24
|
+
function stripTrailingSlash(value) {
|
|
25
|
+
return String(value || '').replace(/\/+$/, '');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function appendPath(baseUrl, path) {
|
|
29
|
+
return `${stripTrailingSlash(baseUrl)}${path.startsWith('/') ? path : `/${path}`}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getArgEndpoint(options = {}) {
|
|
33
|
+
return options.endpoint ||
|
|
34
|
+
null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function resolveTokenBaseUrl(options = {}) {
|
|
38
|
+
const explicitEndpoint = getArgEndpoint(options);
|
|
39
|
+
if (explicitEndpoint) {
|
|
40
|
+
const baseUrl = normalizeBaseUrl(explicitEndpoint, null);
|
|
41
|
+
if (!baseUrl) {
|
|
42
|
+
throw new Error('invalid base_url: pass a valid --endpoint value');
|
|
43
|
+
}
|
|
44
|
+
return baseUrl;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const { config } = getCurrentEnvConfig(options.projectRoot);
|
|
48
|
+
const baseUrl = normalizeBaseUrl(process.env.OPENYIDA_ENDPOINT || config.baseUrl, null);
|
|
49
|
+
if (!baseUrl) {
|
|
50
|
+
throw new Error('missing base_url: pass --endpoint or set OPENYIDA_ENDPOINT');
|
|
51
|
+
}
|
|
52
|
+
return baseUrl;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function resolveDingtalkLoginOrigin(options = {}) {
|
|
56
|
+
if (options.loginOrigin) {
|
|
57
|
+
return options.loginOrigin;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const { config } = getCurrentEnvConfig(options.projectRoot);
|
|
61
|
+
const baseUrl = normalizeBaseUrl(config.baseUrl, '');
|
|
62
|
+
if (baseUrl && baseUrl.includes('yidaapps.com')) {
|
|
63
|
+
return DINGTALK_INTL_LOGIN_ORIGIN;
|
|
64
|
+
}
|
|
65
|
+
} catch {
|
|
66
|
+
// Fall back to domestic DingTalk login.
|
|
67
|
+
}
|
|
68
|
+
return DINGTALK_LOGIN_ORIGIN;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function requestJson(method, url, body, headers = {}) {
|
|
72
|
+
return new Promise((resolve, reject) => {
|
|
73
|
+
const parsedUrl = new URL(url);
|
|
74
|
+
const data = body === undefined || body === null ? null : Buffer.from(JSON.stringify(body));
|
|
75
|
+
const transport = parsedUrl.protocol === 'https:' ? https : http;
|
|
76
|
+
const request = transport.request({
|
|
77
|
+
method,
|
|
78
|
+
protocol: parsedUrl.protocol,
|
|
79
|
+
hostname: parsedUrl.hostname,
|
|
80
|
+
port: parsedUrl.port,
|
|
81
|
+
path: `${parsedUrl.pathname}${parsedUrl.search}`,
|
|
82
|
+
headers: {
|
|
83
|
+
accept: 'application/json',
|
|
84
|
+
...headers,
|
|
85
|
+
...(data ? {
|
|
86
|
+
'content-type': 'application/json',
|
|
87
|
+
'content-length': data.length,
|
|
88
|
+
} : {}),
|
|
89
|
+
},
|
|
90
|
+
}, (response) => {
|
|
91
|
+
const chunks = [];
|
|
92
|
+
response.on('data', (chunk) => chunks.push(chunk));
|
|
93
|
+
response.on('end', () => {
|
|
94
|
+
const text = Buffer.concat(chunks).toString('utf8');
|
|
95
|
+
let payload = null;
|
|
96
|
+
if (text) {
|
|
97
|
+
try {
|
|
98
|
+
payload = JSON.parse(text);
|
|
99
|
+
} catch {
|
|
100
|
+
payload = { raw: text };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (response.statusCode < 200 || response.statusCode >= 300) {
|
|
104
|
+
const location = response.headers && response.headers.location;
|
|
105
|
+
const message = payload && payload.message
|
|
106
|
+
? payload.message
|
|
107
|
+
: `http_${response.statusCode}${location ? `: ${location}` : ''}`;
|
|
108
|
+
const error = new Error(message);
|
|
109
|
+
error.statusCode = response.statusCode;
|
|
110
|
+
error.payload = payload;
|
|
111
|
+
error.headers = response.headers;
|
|
112
|
+
error.location = location;
|
|
113
|
+
reject(error);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
resolve(payload || {});
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
request.on('error', reject);
|
|
121
|
+
if (data) {
|
|
122
|
+
request.write(data);
|
|
123
|
+
}
|
|
124
|
+
request.end();
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function sanitizeRawTokenResponse(response) {
|
|
129
|
+
if (!response || typeof response !== 'object') {
|
|
130
|
+
return response;
|
|
131
|
+
}
|
|
132
|
+
const clone = Array.isArray(response) ? [...response] : { ...response };
|
|
133
|
+
delete clone.ai_app_user_auth_token;
|
|
134
|
+
delete clone.aiAppUserAuthToken;
|
|
135
|
+
delete clone.tianshu_csrf_token;
|
|
136
|
+
delete clone.tianshuCsrfToken;
|
|
137
|
+
delete clone.csrf_token;
|
|
138
|
+
delete clone.csrfToken;
|
|
139
|
+
delete clone.access_token;
|
|
140
|
+
delete clone.accessToken;
|
|
141
|
+
delete clone.refresh_token;
|
|
142
|
+
delete clone.refreshToken;
|
|
143
|
+
if (clone.data && typeof clone.data === 'object') {
|
|
144
|
+
clone.data = sanitizeRawTokenResponse(clone.data);
|
|
145
|
+
}
|
|
146
|
+
if (clone.content && typeof clone.content === 'object') {
|
|
147
|
+
clone.content = sanitizeRawTokenResponse(clone.content);
|
|
148
|
+
}
|
|
149
|
+
return clone;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function getTokenPayload(response) {
|
|
153
|
+
if (!response || typeof response !== 'object') {
|
|
154
|
+
return response || {};
|
|
155
|
+
}
|
|
156
|
+
return response.data || response.content || response;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function normalizeTokenResponse(response, baseUrl, clientId) {
|
|
160
|
+
const data = getTokenPayload(response);
|
|
161
|
+
return {
|
|
162
|
+
auth_mode: 'token',
|
|
163
|
+
status: data.status || 'ok',
|
|
164
|
+
can_auto_use: true,
|
|
165
|
+
message: data.message || data.errorMsg || response.errorMsg,
|
|
166
|
+
token_type: data.token_type || data.tokenType || 'Bearer',
|
|
167
|
+
access_token: data.access_token || data.accessToken,
|
|
168
|
+
refresh_token: data.refresh_token || data.refreshToken,
|
|
169
|
+
expires_in: data.expires_in || data.expiresIn,
|
|
170
|
+
expires_at: data.expires_at || data.expiresAt,
|
|
171
|
+
base_url: baseUrl,
|
|
172
|
+
client_id: data.client_id || data.clientId || clientId,
|
|
173
|
+
corp_id: data.corp_id || data.corpId,
|
|
174
|
+
user_id: data.user_id || data.userId,
|
|
175
|
+
user_name: data.user_name || data.userName,
|
|
176
|
+
open_user_id: data.open_user_id || data.openUserId,
|
|
177
|
+
scope: data.scope,
|
|
178
|
+
raw: sanitizeRawTokenResponse(response),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function requireOkResponse(response) {
|
|
183
|
+
const data = getTokenPayload(response);
|
|
184
|
+
const status = data.status || data.code || response.status || response.code;
|
|
185
|
+
if (typeof status === 'string' && status.startsWith('need_')) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (status && status !== 'ok' && status !== 'success' && status !== true && status !== 200) {
|
|
189
|
+
const error = new Error(data.message || data.error || response.message || response.error || String(status));
|
|
190
|
+
error.payload = response;
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
if (response && response.success === false) {
|
|
194
|
+
const error = new Error(response.errorMsg || response.message || 'request_failed');
|
|
195
|
+
error.payload = response;
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function tokenLogin(options = {}) {
|
|
201
|
+
const baseUrl = resolveTokenBaseUrl(options);
|
|
202
|
+
const authBaseUrl = appendPath(baseUrl, DEFAULT_AUTH_PATH_PREFIX);
|
|
203
|
+
const clientId = options.clientId || process.env.OPENYIDA_DINGTALK_CLIENT_ID || DINGTALK_OAUTH_CLIENT_ID;
|
|
204
|
+
const callback = await runDingtalkLoopback({
|
|
205
|
+
clientId,
|
|
206
|
+
loginOrigin: resolveDingtalkLoginOrigin(options),
|
|
207
|
+
scope: options.scope || process.env.OPENYIDA_DINGTALK_SCOPE || 'openid corpid',
|
|
208
|
+
prompt: options.prompt,
|
|
209
|
+
port: options.port,
|
|
210
|
+
quiet: options.quiet,
|
|
211
|
+
timeoutMs: options.timeoutMs,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
const response = await requestJson('POST', appendPath(authBaseUrl, '/dingtalk/token'), {
|
|
215
|
+
code: callback.code,
|
|
216
|
+
authCode: callback.authCode,
|
|
217
|
+
redirectUri: callback.redirectUri,
|
|
218
|
+
state: callback.state,
|
|
219
|
+
clientId,
|
|
220
|
+
});
|
|
221
|
+
requireOkResponse(response);
|
|
222
|
+
|
|
223
|
+
const normalized = normalizeTokenResponse(response, baseUrl, clientId);
|
|
224
|
+
if (!normalized.access_token) {
|
|
225
|
+
return {
|
|
226
|
+
...normalized,
|
|
227
|
+
ok: false,
|
|
228
|
+
status: 'token_not_issued',
|
|
229
|
+
can_auto_use: false,
|
|
230
|
+
message: 'auth service did not return access_token',
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
return saveTokenSession(normalized, options);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function tokenStatus(options = {}) {
|
|
237
|
+
const session = loadTokenSession(options);
|
|
238
|
+
if (!session || !session.access_token) {
|
|
239
|
+
return {
|
|
240
|
+
ok: false,
|
|
241
|
+
auth_mode: 'token',
|
|
242
|
+
status: 'not_logged_in',
|
|
243
|
+
can_auto_use: false,
|
|
244
|
+
token_file: require('./token-store').getTokenFilePath(options),
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
const usable = isAccessTokenUsable(session);
|
|
248
|
+
return {
|
|
249
|
+
ok: usable,
|
|
250
|
+
auth_mode: 'token',
|
|
251
|
+
status: usable ? 'ok' : 'expired',
|
|
252
|
+
can_auto_use: usable,
|
|
253
|
+
token_type: session.token_type,
|
|
254
|
+
access_token: maskToken(session.access_token),
|
|
255
|
+
refresh_token: maskToken(session.refresh_token),
|
|
256
|
+
expires_at: session.expires_at ? new Date(session.expires_at).toISOString() : undefined,
|
|
257
|
+
base_url: session.base_url,
|
|
258
|
+
corp_id: session.corp_id,
|
|
259
|
+
user_id: session.user_id,
|
|
260
|
+
user_name: session.user_name,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async function tokenRefresh(options = {}) {
|
|
265
|
+
const session = loadTokenSession(options);
|
|
266
|
+
if (!session || !session.refresh_token) {
|
|
267
|
+
return {
|
|
268
|
+
ok: false,
|
|
269
|
+
auth_mode: 'token',
|
|
270
|
+
status: 'missing_refresh_token',
|
|
271
|
+
can_auto_use: false,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const baseUrl = normalizeBaseUrl(options.endpoint || options.baseUrl || session.base_url, null) ||
|
|
276
|
+
resolveTokenBaseUrl(options);
|
|
277
|
+
const authBaseUrl = appendPath(baseUrl, DEFAULT_AUTH_PATH_PREFIX);
|
|
278
|
+
const clientId = options.clientId || session.client_id || DINGTALK_OAUTH_CLIENT_ID;
|
|
279
|
+
const response = await requestJson('POST', appendPath(authBaseUrl, '/refresh'), {
|
|
280
|
+
refreshToken: session.refresh_token,
|
|
281
|
+
clientId,
|
|
282
|
+
});
|
|
283
|
+
requireOkResponse(response);
|
|
284
|
+
const data = getTokenPayload(response);
|
|
285
|
+
const normalized = normalizeTokenResponse({
|
|
286
|
+
...data,
|
|
287
|
+
corp_id: data.corp_id || data.corpId || session.corp_id,
|
|
288
|
+
user_id: data.user_id || data.userId || session.user_id,
|
|
289
|
+
user_name: data.user_name || data.userName || session.user_name,
|
|
290
|
+
refresh_token: data.refresh_token || data.refreshToken || session.refresh_token,
|
|
291
|
+
}, baseUrl, clientId);
|
|
292
|
+
if (!normalized.access_token) {
|
|
293
|
+
return {
|
|
294
|
+
...normalized,
|
|
295
|
+
ok: false,
|
|
296
|
+
status: 'token_not_issued',
|
|
297
|
+
can_auto_use: false,
|
|
298
|
+
message: normalized.message || 'auth service did not return access_token',
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
return saveTokenSession(normalized, options);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async function tokenLogout(options = {}) {
|
|
305
|
+
const session = loadTokenSession(options);
|
|
306
|
+
if (session && session.refresh_token && session.base_url) {
|
|
307
|
+
try {
|
|
308
|
+
await requestJson('POST', appendPath(appendPath(session.base_url, DEFAULT_AUTH_PATH_PREFIX), '/logout'), {
|
|
309
|
+
refreshToken: session.refresh_token,
|
|
310
|
+
}, session.access_token ? {
|
|
311
|
+
authorization: `${session.token_type || 'Bearer'} ${session.access_token}`,
|
|
312
|
+
} : {});
|
|
313
|
+
} catch {
|
|
314
|
+
// Local logout should still clear the credential cache.
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
clearTokenSession(options);
|
|
318
|
+
return {
|
|
319
|
+
ok: true,
|
|
320
|
+
auth_mode: 'token',
|
|
321
|
+
status: 'logged_out',
|
|
322
|
+
can_auto_use: false,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async function getAccessToken(options = {}) {
|
|
327
|
+
let session = loadTokenSession(options);
|
|
328
|
+
if (isAccessTokenUsable(session, options.skewMs)) {
|
|
329
|
+
return session.access_token;
|
|
330
|
+
}
|
|
331
|
+
if (session && session.refresh_token) {
|
|
332
|
+
session = await tokenRefresh(options);
|
|
333
|
+
return session.access_token;
|
|
334
|
+
}
|
|
335
|
+
throw new Error('not_logged_in');
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
module.exports = {
|
|
339
|
+
tokenLogin,
|
|
340
|
+
tokenStatus,
|
|
341
|
+
tokenRefresh,
|
|
342
|
+
tokenLogout,
|
|
343
|
+
getAccessToken,
|
|
344
|
+
requestJson,
|
|
345
|
+
resolveTokenBaseUrl,
|
|
346
|
+
};
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const TOKEN_FILE_PREFIX = 'auth-token';
|
|
7
|
+
|
|
8
|
+
function sanitizeEnvName(envName) {
|
|
9
|
+
return String(envName || 'public').replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function resolveProjectRoot(options = {}) {
|
|
13
|
+
if (options.projectRoot) {
|
|
14
|
+
return options.projectRoot;
|
|
15
|
+
}
|
|
16
|
+
const { findProjectRoot } = require('../core/utils');
|
|
17
|
+
return findProjectRoot();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function resolveEnvName(options = {}) {
|
|
21
|
+
if (options.envName) {
|
|
22
|
+
return sanitizeEnvName(options.envName);
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const { getCurrentEnvConfig, resolveEnvNameAlias } = require('../core/env-manager');
|
|
26
|
+
const { name } = getCurrentEnvConfig(resolveProjectRoot(options));
|
|
27
|
+
return sanitizeEnvName(resolveEnvNameAlias(name || process.env.OPENYIDA_ENV || 'public'));
|
|
28
|
+
} catch {
|
|
29
|
+
return sanitizeEnvName(process.env.OPENYIDA_ENV || 'public');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function getTokenFilePath(options = {}) {
|
|
34
|
+
const root = resolveProjectRoot(options);
|
|
35
|
+
const envName = resolveEnvName(options);
|
|
36
|
+
return path.join(root, '.cache', `${TOKEN_FILE_PREFIX}-${envName}.json`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function normalizeBaseUrl(value) {
|
|
40
|
+
if (!value) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
return new URL(String(value)).origin.replace(/\/+$/, '');
|
|
45
|
+
} catch {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function normalizeExpiresAt(session) {
|
|
51
|
+
if (!session) {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
if (session.expires_at) {
|
|
55
|
+
const value = typeof session.expires_at === 'number'
|
|
56
|
+
? session.expires_at
|
|
57
|
+
: Date.parse(session.expires_at);
|
|
58
|
+
return Number.isFinite(value) ? value : undefined;
|
|
59
|
+
}
|
|
60
|
+
if (session.expiresAt) {
|
|
61
|
+
const value = typeof session.expiresAt === 'number'
|
|
62
|
+
? session.expiresAt
|
|
63
|
+
: Date.parse(session.expiresAt);
|
|
64
|
+
return Number.isFinite(value) ? value : undefined;
|
|
65
|
+
}
|
|
66
|
+
if (session.expires_in || session.expiresIn) {
|
|
67
|
+
const seconds = Number(session.expires_in || session.expiresIn);
|
|
68
|
+
if (Number.isFinite(seconds) && seconds > 0) {
|
|
69
|
+
return Date.now() + seconds * 1000;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function normalizeTokenSession(session = {}) {
|
|
76
|
+
const expiresAt = normalizeExpiresAt(session);
|
|
77
|
+
const baseUrl = normalizeBaseUrl(
|
|
78
|
+
session.base_url ||
|
|
79
|
+
session.baseUrl ||
|
|
80
|
+
session.token_endpoint ||
|
|
81
|
+
session.tokenEndpoint
|
|
82
|
+
);
|
|
83
|
+
const normalized = {
|
|
84
|
+
auth_mode: 'token',
|
|
85
|
+
token_type: session.token_type || session.tokenType || 'Bearer',
|
|
86
|
+
access_token: session.access_token || session.accessToken,
|
|
87
|
+
refresh_token: session.refresh_token || session.refreshToken,
|
|
88
|
+
expires_at: expiresAt,
|
|
89
|
+
issued_at: session.issued_at || session.issuedAt || new Date().toISOString(),
|
|
90
|
+
base_url: baseUrl,
|
|
91
|
+
client_id: session.client_id || session.clientId,
|
|
92
|
+
corp_id: session.corp_id || session.corpId,
|
|
93
|
+
user_id: session.user_id || session.userId,
|
|
94
|
+
user_name: session.user_name || session.userName,
|
|
95
|
+
open_user_id: session.open_user_id || session.openUserId,
|
|
96
|
+
scope: session.scope,
|
|
97
|
+
raw: session.raw,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
Object.keys(normalized).forEach((key) => {
|
|
101
|
+
if (normalized[key] === undefined || normalized[key] === null || normalized[key] === '') {
|
|
102
|
+
delete normalized[key];
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
return normalized;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function saveTokenSession(session, options = {}) {
|
|
109
|
+
const tokenFile = getTokenFilePath(options);
|
|
110
|
+
const normalized = normalizeTokenSession(session);
|
|
111
|
+
fs.mkdirSync(path.dirname(tokenFile), { recursive: true });
|
|
112
|
+
fs.writeFileSync(tokenFile, JSON.stringify(normalized, null, 2), { mode: 0o600 });
|
|
113
|
+
try {
|
|
114
|
+
fs.chmodSync(tokenFile, 0o600);
|
|
115
|
+
} catch {
|
|
116
|
+
// chmod is best-effort on non-POSIX filesystems.
|
|
117
|
+
}
|
|
118
|
+
return normalized;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function loadTokenSession(options = {}) {
|
|
122
|
+
const tokenFile = getTokenFilePath(options);
|
|
123
|
+
if (!fs.existsSync(tokenFile)) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
return normalizeTokenSession(JSON.parse(fs.readFileSync(tokenFile, 'utf8')));
|
|
128
|
+
} catch {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function clearTokenSession(options = {}) {
|
|
134
|
+
const tokenFile = getTokenFilePath(options);
|
|
135
|
+
try {
|
|
136
|
+
if (fs.existsSync(tokenFile)) {
|
|
137
|
+
fs.unlinkSync(tokenFile);
|
|
138
|
+
}
|
|
139
|
+
} catch {
|
|
140
|
+
// Logout should remain idempotent.
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function isAccessTokenUsable(session, skewMs = 60 * 1000) {
|
|
145
|
+
if (!session || !session.access_token) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
if (!session.expires_at) {
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
return Number(session.expires_at) > Date.now() + skewMs;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function maskToken(token) {
|
|
155
|
+
if (!token) {
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
const value = String(token);
|
|
159
|
+
if (value.length <= 16) {
|
|
160
|
+
return `${value.slice(0, 4)}...`;
|
|
161
|
+
}
|
|
162
|
+
return `${value.slice(0, 8)}...${value.slice(-6)}`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
module.exports = {
|
|
166
|
+
getTokenFilePath,
|
|
167
|
+
loadTokenSession,
|
|
168
|
+
saveTokenSession,
|
|
169
|
+
clearTokenSession,
|
|
170
|
+
normalizeTokenSession,
|
|
171
|
+
isAccessTokenUsable,
|
|
172
|
+
maskToken,
|
|
173
|
+
resolveEnvName,
|
|
174
|
+
};
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
|
-
|
|
4
|
+
loadAuthData,
|
|
5
5
|
triggerLogin,
|
|
6
6
|
resolveBaseUrl,
|
|
7
|
-
extractInfoFromCookies,
|
|
8
7
|
} = require('../core/utils');
|
|
9
8
|
const { createYidaClient } = require('../core/yida-client');
|
|
10
9
|
|
|
@@ -128,24 +127,24 @@ async function requestPost(authRef, path, params = {}, actionName = path) {
|
|
|
128
127
|
}
|
|
129
128
|
|
|
130
129
|
async function loadAuth() {
|
|
131
|
-
let
|
|
132
|
-
if (!
|
|
133
|
-
|
|
130
|
+
let authData = loadAuthData();
|
|
131
|
+
if (!authData) {
|
|
132
|
+
authData = await triggerLogin();
|
|
134
133
|
}
|
|
135
134
|
|
|
136
|
-
const
|
|
137
|
-
const csrfToken = cookieData.csrf_token || cookieInfo.csrfToken;
|
|
135
|
+
const csrfToken = authData.csrf_token;
|
|
138
136
|
if (!csrfToken) {
|
|
139
137
|
throw new Error('Missing csrf token. Please run openyida login again.');
|
|
140
138
|
}
|
|
141
139
|
|
|
142
140
|
return {
|
|
143
|
-
|
|
144
|
-
cookies: cookieData.cookies || [],
|
|
141
|
+
authData,
|
|
145
142
|
csrfToken,
|
|
146
|
-
corpId:
|
|
147
|
-
userId:
|
|
148
|
-
baseUrl: resolveBaseUrl(
|
|
143
|
+
corpId: authData.corp_id || '',
|
|
144
|
+
userId: authData.user_id || '',
|
|
145
|
+
baseUrl: resolveBaseUrl(authData),
|
|
146
|
+
authMode: authData.auth_mode || '',
|
|
147
|
+
authSource: authData.auth_source || '',
|
|
149
148
|
};
|
|
150
149
|
}
|
|
151
150
|
|
package/lib/bridge/bridge.js
CHANGED
|
@@ -23,16 +23,18 @@ const SAFE_DIAGNOSTIC_KEYS = [
|
|
|
23
23
|
'currentEnv',
|
|
24
24
|
'projectRootExists',
|
|
25
25
|
'hasConfig',
|
|
26
|
-
'cookieFileFound',
|
|
27
|
-
'usedLegacyCookieFile',
|
|
28
26
|
'cache_readable',
|
|
29
|
-
'
|
|
30
|
-
'
|
|
27
|
+
'cacheReadable',
|
|
28
|
+
'tokenFileFound',
|
|
29
|
+
'tokenFound',
|
|
31
30
|
'corp_id_found',
|
|
31
|
+
'corpIdFound',
|
|
32
32
|
'user_id_found',
|
|
33
|
+
'userIdFound',
|
|
33
34
|
'base_url_found',
|
|
35
|
+
'baseUrlFound',
|
|
34
36
|
'failure_reason',
|
|
35
|
-
'
|
|
37
|
+
'failureReason',
|
|
36
38
|
];
|
|
37
39
|
|
|
38
40
|
function printUsage() {
|
|
@@ -511,11 +513,11 @@ function sanitizeLoginResult(result = {}) {
|
|
|
511
513
|
return {
|
|
512
514
|
status,
|
|
513
515
|
canAutoUse: !!(result.can_auto_use || result.canAutoUse),
|
|
514
|
-
loggedIn: status === 'ok'
|
|
516
|
+
loggedIn: status === 'ok',
|
|
517
|
+
authMode: result.auth_mode || result.authMode,
|
|
515
518
|
baseUrlOrigin: originFromUrl(result.base_url || result.baseUrl),
|
|
516
519
|
corpId: maskId(result.corp_id || result.corpId),
|
|
517
520
|
userId: maskId(result.user_id || result.userId),
|
|
518
|
-
cookiesCount: result.cookies_count || (Array.isArray(result.cookies) ? result.cookies.length : undefined),
|
|
519
521
|
diagnostics: sanitizeDiagnostics(result.diagnostics || {}),
|
|
520
522
|
message: result.message ? String(result.message).slice(0, 300) : undefined,
|
|
521
523
|
};
|
|
@@ -549,9 +551,6 @@ function getBridgeInfo(state) {
|
|
|
549
551
|
capabilities: [
|
|
550
552
|
'pair',
|
|
551
553
|
'login.check',
|
|
552
|
-
'login.start',
|
|
553
|
-
'login.poll',
|
|
554
|
-
'login.select-corp',
|
|
555
554
|
'feedback.url',
|
|
556
555
|
'openyida.app-list',
|
|
557
556
|
'openyida.create-app',
|
|
@@ -560,32 +559,35 @@ function getBridgeInfo(state) {
|
|
|
560
559
|
}
|
|
561
560
|
|
|
562
561
|
function defaultCheckLogin() {
|
|
563
|
-
const {
|
|
564
|
-
return
|
|
562
|
+
const { tokenStatus } = require('../auth/token-auth');
|
|
563
|
+
return tokenStatus({ includeSecrets: false });
|
|
565
564
|
}
|
|
566
565
|
|
|
567
566
|
async function defaultStartLogin(body = {}) {
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
567
|
+
return {
|
|
568
|
+
status: 'token_login_required',
|
|
569
|
+
auth_mode: 'token',
|
|
570
|
+
can_auto_use: false,
|
|
571
|
+
message: 'Token-only auth does not support bridge QR login. Run openyida login first.',
|
|
572
|
+
};
|
|
573
573
|
}
|
|
574
574
|
|
|
575
575
|
async function defaultPollLogin(sessionFile, body = {}) {
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
}
|
|
576
|
+
return {
|
|
577
|
+
status: 'token_login_required',
|
|
578
|
+
auth_mode: 'token',
|
|
579
|
+
can_auto_use: false,
|
|
580
|
+
message: 'Token-only auth does not support bridge QR polling. Run openyida login first.',
|
|
581
|
+
};
|
|
582
582
|
}
|
|
583
583
|
|
|
584
584
|
async function defaultSelectCorp(sessionFile, body = {}) {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
585
|
+
return {
|
|
586
|
+
status: 'token_login_required',
|
|
587
|
+
auth_mode: 'token',
|
|
588
|
+
can_auto_use: false,
|
|
589
|
+
message: 'Token-only auth does not support bridge organization selection.',
|
|
590
|
+
};
|
|
589
591
|
}
|
|
590
592
|
|
|
591
593
|
function defaultReadFeedbackConfig(configPath) {
|
package/lib/connector/api.js
CHANGED
|
@@ -13,7 +13,7 @@ const { createAuthRef, createYidaClient } = require('../core/yida-client');
|
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* 获取当前登录态,未登录则触发登录
|
|
16
|
-
* @returns {{ csrfToken: string,
|
|
16
|
+
* @returns {{ csrfToken: string, baseUrl: string, authData: object }}
|
|
17
17
|
*/
|
|
18
18
|
function getAuthRef() {
|
|
19
19
|
return createAuthRef();
|
|
@@ -80,15 +80,15 @@ function buildOperationsSummary(operations) {
|
|
|
80
80
|
* 构建连接器描述(包含 openyida 元数据)
|
|
81
81
|
* @param {string|null} userDesc - 用户自定义描述(可选)
|
|
82
82
|
* @param {string|null} originalDesc - 原有描述(用于提取创建信息)
|
|
83
|
-
* @param {object} authRef - 鉴权信息(含
|
|
83
|
+
* @param {object} authRef - 鉴权信息(含 authData)
|
|
84
84
|
* @param {Array|null} operations - 当前所有动作列表
|
|
85
85
|
* @returns {string}
|
|
86
86
|
*/
|
|
87
87
|
function buildConnectorDesc(userDesc, originalDesc, authRef, operations) {
|
|
88
88
|
const now = new Date();
|
|
89
89
|
const updateTime = now.toLocaleString('zh-CN');
|
|
90
|
-
const currentUserId = authRef.
|
|
91
|
-
? authRef.
|
|
90
|
+
const currentUserId = authRef.userId || (authRef.authData && authRef.authData.user_id)
|
|
91
|
+
? authRef.userId || authRef.authData.user_id
|
|
92
92
|
: '';
|
|
93
93
|
|
|
94
94
|
let createTime = updateTime;
|