@xfe-repo/cli-plugin-oauth 2.0.8 → 2.0.10
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/CHANGELOG.md +12 -0
- package/dist/callback-server.d.ts +1 -1
- package/dist/callback-server.js +26 -15
- package/dist/index.d.ts +4 -7
- package/dist/index.js +54 -117
- package/dist/types.d.ts +1 -6
- package/package.json +3 -7
- package/dist/callback-runner.d.ts +0 -8
- package/dist/callback-runner.js +0 -26
package/CHANGELOG.md
CHANGED
package/dist/callback-server.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @xfe-repo/cli-plugin-oauth - OAuth 回调服务
|
|
3
3
|
*
|
|
4
|
-
* 基于
|
|
4
|
+
* 基于 Node HTTP 启动临时本地服务,等待 OAuth 授权服务器回跳 redirectUri。
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
7
|
-
import { Hono } from 'hono';
|
|
6
|
+
import { createServer } from 'node:http';
|
|
8
7
|
// ─── Constants ─────────────────────────────────────────────
|
|
9
8
|
const CALLBACK_TIMEOUT_MS = 300000;
|
|
10
9
|
const HTTP_OK = 200;
|
|
11
10
|
const HTTP_BAD_REQUEST = 400;
|
|
12
11
|
const HTTP_NOT_FOUND = 404;
|
|
12
|
+
const HTML_CONTENT_TYPE = 'text/html; charset=utf-8';
|
|
13
13
|
const SUCCESS_HTML = '<!doctype html><html lang="zh"><body>OAuth 登录完成,可以关闭此窗口。</body></html>';
|
|
14
14
|
const FAILURE_HTML = '<!doctype html><html lang="zh"><body>OAuth 登录失败,请回到终端查看错误。</body></html>';
|
|
15
15
|
// ─── Public API ────────────────────────────────────────────
|
|
@@ -32,23 +32,31 @@ export async function startOAuthCallbackServer(redirectUri) {
|
|
|
32
32
|
resolveCallback = resolve;
|
|
33
33
|
rejectCallback = reject;
|
|
34
34
|
});
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
const httpServer = createServer((request, response) => {
|
|
36
|
+
const currentUrl = new URL(request.url ?? '/', redirectUrl.origin);
|
|
37
|
+
if (currentUrl.pathname !== redirectUrl.pathname) {
|
|
38
|
+
writeHtml(response, HTTP_NOT_FOUND, FAILURE_HTML);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
38
41
|
const error = currentUrl.searchParams.get('error');
|
|
39
42
|
if (error) {
|
|
40
43
|
const description = currentUrl.searchParams.get('error_description');
|
|
41
44
|
rejectCallback(new Error(description ? `${error}: ${description}` : error));
|
|
42
|
-
|
|
45
|
+
writeHtml(response, HTTP_BAD_REQUEST, FAILURE_HTML);
|
|
46
|
+
return;
|
|
43
47
|
}
|
|
44
48
|
resolveCallback(currentUrl);
|
|
45
|
-
|
|
49
|
+
writeHtml(response, HTTP_OK, SUCCESS_HTML);
|
|
46
50
|
});
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
await new Promise((resolve, reject) => {
|
|
52
|
+
const handleError = (error) => {
|
|
53
|
+
reject(error);
|
|
54
|
+
};
|
|
55
|
+
httpServer.once('error', handleError);
|
|
56
|
+
httpServer.listen(port, redirectUrl.hostname, () => {
|
|
57
|
+
httpServer.off('error', handleError);
|
|
58
|
+
resolve();
|
|
59
|
+
});
|
|
52
60
|
});
|
|
53
61
|
const timeout = setTimeout(() => {
|
|
54
62
|
rejectCallback(new Error('OAuth 登录超时,请重新执行 oauth:login'));
|
|
@@ -61,14 +69,17 @@ export async function startOAuthCallbackServer(redirectUri) {
|
|
|
61
69
|
hasClosed = true;
|
|
62
70
|
clearTimeout(timeout);
|
|
63
71
|
rejectCallback(new Error('OAuth 回调服务已关闭'));
|
|
64
|
-
const closableServer = httpServer;
|
|
65
72
|
await new Promise((resolve) => {
|
|
66
73
|
httpServer.close(() => {
|
|
67
74
|
resolve();
|
|
68
75
|
});
|
|
69
|
-
|
|
76
|
+
httpServer.closeAllConnections();
|
|
70
77
|
});
|
|
71
78
|
},
|
|
72
79
|
};
|
|
73
80
|
}
|
|
81
|
+
function writeHtml(response, status, html) {
|
|
82
|
+
response.writeHead(status, { 'Content-Type': HTML_CONTENT_TYPE });
|
|
83
|
+
response.end(html);
|
|
84
|
+
}
|
|
74
85
|
//# sourceMappingURL=callback-server.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
* @xfe-repo/cli-plugin-oauth - OAuth 登录插件
|
|
3
3
|
*
|
|
4
4
|
* 通过 openid-client 对接 OIDC/OAuth 登录,保存本地 token 状态,
|
|
5
|
-
* 并向其他插件提供
|
|
5
|
+
* 并向其他插件提供 login/getToken/getUser/logout 能力。
|
|
6
6
|
*/
|
|
7
7
|
import type { XfePlugin } from '@xfe-repo/cli-core';
|
|
8
|
-
import { type OAuthCapability, type
|
|
9
|
-
export type { OAuthCapability, OAuthConfig, OAuthLoginOptions, OAuthSessionState, OAuthStoreState,
|
|
8
|
+
import { type OAuthCapability, type OAuthStoreState } from './types.js';
|
|
9
|
+
export type { OAuthCapability, OAuthConfig, OAuthLoginOptions, OAuthSessionState, OAuthStoreState, OAuthUserState, } from './types.js';
|
|
10
10
|
export { OAuthClientAuthMethod } from './types.js';
|
|
11
|
-
|
|
11
|
+
declare const oauthStoreInitial: OAuthStoreState;
|
|
12
12
|
declare module '@xfe-repo/cli-core' {
|
|
13
13
|
interface XfePluginRegistry {
|
|
14
14
|
oauth: XfePlugin<typeof oauthStoreInitial, OAuthCapability>;
|
|
@@ -16,7 +16,4 @@ declare module '@xfe-repo/cli-core' {
|
|
|
16
16
|
}
|
|
17
17
|
export declare function oauthPlugin(): XfePlugin<typeof oauthStoreInitial, OAuthCapability>;
|
|
18
18
|
export default oauthPlugin;
|
|
19
|
-
export declare function resolveOAuthStateForConfig(config: OAuthConfig, state?: Partial<OAuthStoreState>): OAuthTokenState;
|
|
20
|
-
export declare function resolveOAuthConfigKey(config: OAuthConfig): string;
|
|
21
|
-
export declare function normalizeOAuthUserInfo(input: unknown): OAuthUserState;
|
|
22
19
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @xfe-repo/cli-plugin-oauth - OAuth 登录插件
|
|
3
3
|
*
|
|
4
4
|
* 通过 openid-client 对接 OIDC/OAuth 登录,保存本地 token 状态,
|
|
5
|
-
* 并向其他插件提供
|
|
5
|
+
* 并向其他插件提供 login/getToken/getUser/logout 能力。
|
|
6
6
|
*/
|
|
7
7
|
import { createHash } from 'node:crypto';
|
|
8
8
|
import * as oauthClient from 'openid-client';
|
|
@@ -25,7 +25,7 @@ const OAUTH_CONFIG_KEY_VERSION = 'v1';
|
|
|
25
25
|
const OAUTH_CONFIG_KEY_SEPARATOR = '\n';
|
|
26
26
|
const OAUTH_CONFIG_HASH_ALGORITHM = 'sha256';
|
|
27
27
|
const OAUTH_CONFIG_HASH_ENCODING = 'hex';
|
|
28
|
-
|
|
28
|
+
const oauthStoreInitial = {};
|
|
29
29
|
const oauthConfigSchema = z
|
|
30
30
|
.object({
|
|
31
31
|
name: z.string().trim().min(1).default(DEFAULT_OAUTH_NAME),
|
|
@@ -59,19 +59,15 @@ export function oauthPlugin() {
|
|
|
59
59
|
slot: ['footer'],
|
|
60
60
|
value: (state) => {
|
|
61
61
|
const currentSession = resolveOptionalOAuthState(state.xfe, state.oauth);
|
|
62
|
-
|
|
62
|
+
const isAuthenticated = currentSession && isOAuthAccessTokenValid(currentSession, Math.floor(Date.now() / MILLISECONDS_PER_SECOND));
|
|
63
|
+
return currentSession ? `Oauth ${isAuthenticated ? '已登录' : '未登录'}` : undefined;
|
|
63
64
|
},
|
|
64
65
|
color: (state) => {
|
|
65
66
|
const currentSession = resolveOptionalOAuthState(state.xfe, state.oauth);
|
|
66
|
-
|
|
67
|
+
const isAuthenticated = currentSession && isOAuthAccessTokenValid(currentSession, Math.floor(Date.now() / MILLISECONDS_PER_SECOND));
|
|
68
|
+
return isAuthenticated ? 'cyan' : 'yellow';
|
|
67
69
|
},
|
|
68
70
|
});
|
|
69
|
-
hooks.onProjectInit.tap(OAUTH_PLUGIN_NAME, async (ctx) => {
|
|
70
|
-
const config = parseOptionalOAuthConfig(ctx.store.getState().xfe);
|
|
71
|
-
if (!config)
|
|
72
|
-
return;
|
|
73
|
-
replaceOAuthStoreState(ctx, createOAuthStoreState(ctx.store.getState().oauth));
|
|
74
|
-
});
|
|
75
71
|
hooks.registerCommand({
|
|
76
72
|
name: 'oauth:login',
|
|
77
73
|
simpleDescription: '登录 OAuth 平台',
|
|
@@ -89,7 +85,6 @@ export function oauthPlugin() {
|
|
|
89
85
|
},
|
|
90
86
|
});
|
|
91
87
|
hooks.provide('oauth', {
|
|
92
|
-
auth: (ctx, options) => login(ctx, options, runtime),
|
|
93
88
|
login: (ctx, options) => login(ctx, options, runtime),
|
|
94
89
|
getToken: (ctx) => getToken(ctx, runtime),
|
|
95
90
|
getUser: (ctx) => getUser(ctx, runtime),
|
|
@@ -108,12 +103,9 @@ export default oauthPlugin;
|
|
|
108
103
|
// ─── Capability Handlers ───────────────────────────────────
|
|
109
104
|
async function login(ctx, options, runtime) {
|
|
110
105
|
const config = parseOAuthConfig(ctx.store.getState().xfe);
|
|
111
|
-
const current =
|
|
106
|
+
const current = resolveOAuthStateForConfig(config, ctx.store.getState().oauth);
|
|
112
107
|
const now = Math.floor(Date.now() / MILLISECONDS_PER_SECOND);
|
|
113
|
-
if (!options?.force &&
|
|
114
|
-
current.isAuthenticated &&
|
|
115
|
-
current.accessToken &&
|
|
116
|
-
(!current.expiresAt || current.expiresAt - TOKEN_EXPIRY_SKEW_SECONDS > now)) {
|
|
108
|
+
if (!options?.force && isOAuthAccessTokenValid(current, now)) {
|
|
117
109
|
return current;
|
|
118
110
|
}
|
|
119
111
|
const spinner = ctx.spinner.create(`正在登录 ${config.name}...`);
|
|
@@ -151,19 +143,7 @@ async function login(ctx, options, runtime) {
|
|
|
151
143
|
}
|
|
152
144
|
if (!tokenResponse.access_token)
|
|
153
145
|
throw new Error('OAuth token 响应缺少 access_token');
|
|
154
|
-
const
|
|
155
|
-
const tokenState = {
|
|
156
|
-
name: config.name,
|
|
157
|
-
accessToken: tokenResponse.access_token,
|
|
158
|
-
refreshToken: tokenResponse.refresh_token || '',
|
|
159
|
-
idToken: tokenResponse.id_token || '',
|
|
160
|
-
tokenType: tokenResponse.token_type || DEFAULT_TOKEN_TYPE,
|
|
161
|
-
scope: tokenResponse.scope || '',
|
|
162
|
-
expiresAt: expiresIn ? now + expiresIn : 0,
|
|
163
|
-
isAuthenticated: true,
|
|
164
|
-
userId: 0,
|
|
165
|
-
userName: '',
|
|
166
|
-
};
|
|
146
|
+
const tokenState = createOAuthSessionFromToken(config, tokenResponse, now);
|
|
167
147
|
saveOAuthSession(ctx, config, tokenState);
|
|
168
148
|
spinner.succeed(`${config.name} 登录成功`);
|
|
169
149
|
return resolveOAuthStateForConfig(config, ctx.store.getState().oauth);
|
|
@@ -176,9 +156,9 @@ async function login(ctx, options, runtime) {
|
|
|
176
156
|
}
|
|
177
157
|
async function getToken(ctx, runtime) {
|
|
178
158
|
const config = parseOAuthConfig(ctx.store.getState().xfe);
|
|
179
|
-
const current =
|
|
159
|
+
const current = resolveOAuthStateForConfig(config, ctx.store.getState().oauth);
|
|
180
160
|
const now = Math.floor(Date.now() / MILLISECONDS_PER_SECOND);
|
|
181
|
-
if (
|
|
161
|
+
if (isOAuthAccessTokenValid(current, now)) {
|
|
182
162
|
return current.accessToken;
|
|
183
163
|
}
|
|
184
164
|
if (current.refreshToken) {
|
|
@@ -187,19 +167,7 @@ async function getToken(ctx, runtime) {
|
|
|
187
167
|
const tokenResponse = await oauthClient.refreshTokenGrant(clientConfig, current.refreshToken);
|
|
188
168
|
if (!tokenResponse.access_token)
|
|
189
169
|
throw new Error('OAuth token 响应缺少 access_token');
|
|
190
|
-
const
|
|
191
|
-
const tokenState = {
|
|
192
|
-
name: config.name,
|
|
193
|
-
accessToken: tokenResponse.access_token,
|
|
194
|
-
refreshToken: tokenResponse.refresh_token || current.refreshToken,
|
|
195
|
-
idToken: tokenResponse.id_token || '',
|
|
196
|
-
tokenType: tokenResponse.token_type || DEFAULT_TOKEN_TYPE,
|
|
197
|
-
scope: tokenResponse.scope || '',
|
|
198
|
-
expiresAt: expiresIn ? now + expiresIn : 0,
|
|
199
|
-
isAuthenticated: true,
|
|
200
|
-
userId: 0,
|
|
201
|
-
userName: '',
|
|
202
|
-
};
|
|
170
|
+
const tokenState = createOAuthSessionFromToken(config, tokenResponse, now, current.refreshToken);
|
|
203
171
|
saveOAuthSession(ctx, config, tokenState);
|
|
204
172
|
return tokenState.accessToken;
|
|
205
173
|
}
|
|
@@ -213,7 +181,7 @@ async function getToken(ctx, runtime) {
|
|
|
213
181
|
}
|
|
214
182
|
async function getUser(ctx, runtime) {
|
|
215
183
|
const config = parseOAuthConfig(ctx.store.getState().xfe);
|
|
216
|
-
const current =
|
|
184
|
+
const current = resolveOAuthStateForConfig(config, ctx.store.getState().oauth);
|
|
217
185
|
if (current.userId > 0 && current.userName)
|
|
218
186
|
return { userId: current.userId, userName: current.userName };
|
|
219
187
|
if (!config.userInfoUrl)
|
|
@@ -231,24 +199,34 @@ async function getUser(ctx, runtime) {
|
|
|
231
199
|
}
|
|
232
200
|
const user = normalizeOAuthUserInfo(await response.json());
|
|
233
201
|
const latest = resolveOAuthStateForConfig(config, ctx.store.getState().oauth);
|
|
234
|
-
saveOAuthSession(ctx, config, { ...
|
|
202
|
+
saveOAuthSession(ctx, config, { ...latest, ...user });
|
|
235
203
|
return user;
|
|
236
204
|
}
|
|
237
205
|
async function logout(ctx) {
|
|
238
206
|
const config = parseOptionalOAuthConfig(ctx.store.getState().xfe);
|
|
239
207
|
const current = config ? resolveOAuthStateForConfig(config, ctx.store.getState().oauth) : undefined;
|
|
240
208
|
const name = config?.name || current?.name || DEFAULT_OAUTH_NAME;
|
|
241
|
-
const storeState =
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
209
|
+
const storeState = ctx.store.getState().oauth ?? oauthStoreInitial;
|
|
210
|
+
const currentConfigKey = config ? resolveOAuthConfigKey(config) : undefined;
|
|
211
|
+
const nextStoreState = currentConfigKey
|
|
212
|
+
? Object.fromEntries(Object.entries(storeState).filter(([configKey]) => configKey !== currentConfigKey))
|
|
213
|
+
: storeState;
|
|
214
|
+
ctx.store.setState({ oauth: nextStoreState });
|
|
245
215
|
ctx.logger.success(`${name} 已退出登录`);
|
|
246
216
|
}
|
|
247
217
|
// ─── Config And State ──────────────────────────────────────
|
|
248
|
-
|
|
218
|
+
function resolveOAuthStateForConfig(config, state) {
|
|
249
219
|
const configKey = resolveOAuthConfigKey(config);
|
|
250
220
|
const session = state?.[configKey];
|
|
251
|
-
return
|
|
221
|
+
return {
|
|
222
|
+
name: session?.name || config.name,
|
|
223
|
+
accessToken: session?.accessToken || '',
|
|
224
|
+
refreshToken: session?.refreshToken || '',
|
|
225
|
+
tokenType: session?.tokenType || DEFAULT_TOKEN_TYPE,
|
|
226
|
+
expiresAt: session?.expiresAt ?? 0,
|
|
227
|
+
userId: session?.userId ?? 0,
|
|
228
|
+
userName: session?.userName || '',
|
|
229
|
+
};
|
|
252
230
|
}
|
|
253
231
|
function parseOAuthConfig(xfeConfig) {
|
|
254
232
|
const config = parseOptionalOAuthConfig(xfeConfig);
|
|
@@ -267,19 +245,11 @@ function parseOptionalOAuthConfig(xfeConfig) {
|
|
|
267
245
|
}
|
|
268
246
|
return result.data;
|
|
269
247
|
}
|
|
270
|
-
function activateOAuthState(ctx, config) {
|
|
271
|
-
const storeState = createOAuthStoreState(ctx.store.getState().oauth);
|
|
272
|
-
replaceOAuthStoreState(ctx, storeState);
|
|
273
|
-
return resolveOAuthStateForConfig(config, storeState);
|
|
274
|
-
}
|
|
275
248
|
function saveOAuthSession(ctx, config, session) {
|
|
276
249
|
const configKey = resolveOAuthConfigKey(config);
|
|
277
|
-
const storeState =
|
|
278
|
-
storeState[configKey] =
|
|
279
|
-
|
|
280
|
-
}
|
|
281
|
-
function replaceOAuthStoreState(ctx, state) {
|
|
282
|
-
ctx.store.setState({ oauth: state });
|
|
250
|
+
const storeState = { ...(ctx.store.getState().oauth ?? oauthStoreInitial) };
|
|
251
|
+
storeState[configKey] = session;
|
|
252
|
+
ctx.store.setState({ oauth: storeState });
|
|
283
253
|
}
|
|
284
254
|
function resolveOptionalOAuthState(xfeConfig, state) {
|
|
285
255
|
const config = parseOptionalOAuthConfig(xfeConfig);
|
|
@@ -287,10 +257,7 @@ function resolveOptionalOAuthState(xfeConfig, state) {
|
|
|
287
257
|
return undefined;
|
|
288
258
|
return resolveOAuthStateForConfig(config, state);
|
|
289
259
|
}
|
|
290
|
-
function
|
|
291
|
-
return cloneOAuthState(state);
|
|
292
|
-
}
|
|
293
|
-
export function resolveOAuthConfigKey(config) {
|
|
260
|
+
function resolveOAuthConfigKey(config) {
|
|
294
261
|
const payload = [
|
|
295
262
|
OAUTH_CONFIG_KEY_VERSION,
|
|
296
263
|
config.discoveryUrl ?? '',
|
|
@@ -305,37 +272,22 @@ export function resolveOAuthConfigKey(config) {
|
|
|
305
272
|
const digest = createHash(OAUTH_CONFIG_HASH_ALGORITHM).update(payload).digest(OAUTH_CONFIG_HASH_ENCODING);
|
|
306
273
|
return `${OAUTH_CONFIG_KEY_PREFIX}:${digest}`;
|
|
307
274
|
}
|
|
308
|
-
function
|
|
309
|
-
|
|
310
|
-
return {};
|
|
311
|
-
return Object.fromEntries(Object.entries(stateMap)
|
|
312
|
-
.filter((entry) => isOAuthSessionState(entry[1]))
|
|
313
|
-
.map(([configKey, session]) => [configKey, toOAuthSessionState(session, session.name)]));
|
|
275
|
+
function isOAuthAccessTokenValid(state, now) {
|
|
276
|
+
return !!state.accessToken && (!state.expiresAt || state.expiresAt - TOKEN_EXPIRY_SKEW_SECONDS > now);
|
|
314
277
|
}
|
|
315
|
-
function
|
|
316
|
-
|
|
317
|
-
return false;
|
|
318
|
-
const record = value;
|
|
319
|
-
return typeof record.name === 'string';
|
|
320
|
-
}
|
|
321
|
-
function createEmptyOAuthSession(name) {
|
|
322
|
-
return toOAuthSessionState(undefined, name);
|
|
323
|
-
}
|
|
324
|
-
function toOAuthSessionState(state, name) {
|
|
278
|
+
function createOAuthSessionFromToken(config, tokenResponse, now, fallbackRefreshToken = '') {
|
|
279
|
+
const expiresIn = tokenResponse.expiresIn();
|
|
325
280
|
return {
|
|
326
|
-
name:
|
|
327
|
-
accessToken:
|
|
328
|
-
refreshToken:
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
isAuthenticated: state?.isAuthenticated ?? false,
|
|
334
|
-
userId: state?.userId ?? 0,
|
|
335
|
-
userName: state?.userName || '',
|
|
281
|
+
name: config.name,
|
|
282
|
+
accessToken: tokenResponse.access_token ?? '',
|
|
283
|
+
refreshToken: tokenResponse.refresh_token || fallbackRefreshToken,
|
|
284
|
+
tokenType: tokenResponse.token_type || DEFAULT_TOKEN_TYPE,
|
|
285
|
+
expiresAt: expiresIn ? now + expiresIn : 0,
|
|
286
|
+
userId: 0,
|
|
287
|
+
userName: '',
|
|
336
288
|
};
|
|
337
289
|
}
|
|
338
|
-
|
|
290
|
+
function normalizeOAuthUserInfo(input) {
|
|
339
291
|
if (typeof input !== 'object' || input === null || Array.isArray(input)) {
|
|
340
292
|
throw new Error('OAuth 用户信息响应格式无效');
|
|
341
293
|
}
|
|
@@ -344,31 +296,16 @@ export function normalizeOAuthUserInfo(input) {
|
|
|
344
296
|
const userRecord = nestedRecord ?? rootRecord;
|
|
345
297
|
const userIdKeys = ['userId', 'user_id', 'UserId', 'id', 'uid'];
|
|
346
298
|
const userNameKeys = ['name', 'userName', 'user_name', 'UserName', 'username', 'nickName', 'nikeName', 'nickname'];
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
if (typeof value === 'number' && Number.isInteger(value)) {
|
|
352
|
-
userId = value;
|
|
353
|
-
break;
|
|
354
|
-
}
|
|
355
|
-
if (typeof value === 'string' && value.trim()) {
|
|
356
|
-
const parsed = Number.parseInt(value, 10);
|
|
357
|
-
if (!Number.isNaN(parsed)) {
|
|
358
|
-
userId = parsed;
|
|
359
|
-
break;
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
}
|
|
299
|
+
const userId = userIdKeys
|
|
300
|
+
.map((key) => userRecord[key])
|
|
301
|
+
.map((value) => (typeof value === 'number' ? value : typeof value === 'string' ? Number.parseInt(value, 10) : Number.NaN))
|
|
302
|
+
.find(Number.isInteger) ?? 0;
|
|
363
303
|
if (!userId)
|
|
364
304
|
throw new Error('OAuth 用户信息缺少 userId');
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
break;
|
|
370
|
-
}
|
|
371
|
-
}
|
|
305
|
+
const userName = userNameKeys
|
|
306
|
+
.map((key) => userRecord[key])
|
|
307
|
+
.find((value) => typeof value === 'string' && !!value.trim())
|
|
308
|
+
?.trim();
|
|
372
309
|
if (!userName)
|
|
373
310
|
throw new Error('OAuth 用户信息缺少 userName');
|
|
374
311
|
return { userId, userName };
|
package/dist/types.d.ts
CHANGED
|
@@ -24,16 +24,12 @@ export interface OAuthSessionState {
|
|
|
24
24
|
name: string;
|
|
25
25
|
accessToken: string;
|
|
26
26
|
refreshToken: string;
|
|
27
|
-
idToken: string;
|
|
28
27
|
tokenType: string;
|
|
29
|
-
scope: string;
|
|
30
28
|
expiresAt: number;
|
|
31
|
-
isAuthenticated: boolean;
|
|
32
29
|
userId: number;
|
|
33
30
|
userName: string;
|
|
34
31
|
}
|
|
35
32
|
export type OAuthStoreState = Record<string, OAuthSessionState>;
|
|
36
|
-
export type OAuthTokenState = OAuthSessionState;
|
|
37
33
|
export interface OAuthLoginOptions {
|
|
38
34
|
force?: boolean;
|
|
39
35
|
}
|
|
@@ -42,8 +38,7 @@ export interface OAuthUserState {
|
|
|
42
38
|
userName: string;
|
|
43
39
|
}
|
|
44
40
|
export interface OAuthCapability {
|
|
45
|
-
|
|
46
|
-
login(ctx: PluginContext, options?: OAuthLoginOptions): Promise<OAuthTokenState>;
|
|
41
|
+
login(ctx: PluginContext, options?: OAuthLoginOptions): Promise<OAuthSessionState>;
|
|
47
42
|
getToken(ctx: PluginContext): Promise<string>;
|
|
48
43
|
getUser(ctx: PluginContext): Promise<OAuthUserState>;
|
|
49
44
|
logout(ctx: PluginContext): Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xfe-repo/cli-plugin-oauth",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.10",
|
|
4
4
|
"description": "XFE OAuth plugin - login through OIDC/OAuth and expose token state",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@hono/node-server": "^1.14.0",
|
|
10
|
-
"hono": "^4.7.0",
|
|
11
9
|
"openid-client": "^6.8.1",
|
|
12
10
|
"open": "^10.2.0",
|
|
13
11
|
"zod": "^4.3.6",
|
|
@@ -15,8 +13,7 @@
|
|
|
15
13
|
},
|
|
16
14
|
"devDependencies": {
|
|
17
15
|
"@types/node": "^24.3.0",
|
|
18
|
-
"typescript": "^5.9.2"
|
|
19
|
-
"vitest": "^3.1.0"
|
|
16
|
+
"typescript": "^5.9.2"
|
|
20
17
|
},
|
|
21
18
|
"files": [
|
|
22
19
|
"dist",
|
|
@@ -26,8 +23,7 @@
|
|
|
26
23
|
"license": "ISC",
|
|
27
24
|
"scripts": {
|
|
28
25
|
"dev": "tsc -p tsconfig.json -w",
|
|
29
|
-
"build": "tsc -p tsconfig.json",
|
|
30
|
-
"test": "vitest run",
|
|
26
|
+
"build": "pnpm clean && tsc -p tsconfig.json",
|
|
31
27
|
"clean": "rm -rf dist"
|
|
32
28
|
}
|
|
33
29
|
}
|
package/dist/callback-runner.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @xfe-repo/cli-plugin-oauth - OAuth 回调子进程
|
|
3
|
-
*
|
|
4
|
-
* 在独立 Node 进程中等待本地 OAuth 回调,让 CLI session 的 Ctrl+C
|
|
5
|
-
* 可以通过 ctx.exec 终止等待中的回调服务。
|
|
6
|
-
*/
|
|
7
|
-
import { OAUTH_CALLBACK_URL_PREFIX, startOAuthCallbackServer } from './callback-server.js';
|
|
8
|
-
async function main() {
|
|
9
|
-
const redirectUri = process.argv[2];
|
|
10
|
-
if (!redirectUri)
|
|
11
|
-
throw new Error('缺少 OAuth redirectUri 参数');
|
|
12
|
-
const callbackServer = await startOAuthCallbackServer(redirectUri);
|
|
13
|
-
try {
|
|
14
|
-
const callbackUrl = await callbackServer.waitForCallback();
|
|
15
|
-
process.stdout.write(`${OAUTH_CALLBACK_URL_PREFIX}${callbackUrl.href}\n`);
|
|
16
|
-
}
|
|
17
|
-
finally {
|
|
18
|
-
await callbackServer.close();
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
main().catch((error) => {
|
|
22
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
23
|
-
process.stderr.write(`${message}\n`);
|
|
24
|
-
process.exitCode = 1;
|
|
25
|
-
});
|
|
26
|
-
//# sourceMappingURL=callback-runner.js.map
|