@xfe-repo/cli-plugin-oauth 2.0.9 → 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 +6 -0
- package/dist/index.js +31 -67
- package/dist/types.d.ts +0 -3
- package/package.json +3 -5
- package/dist/callback-runner.d.ts +0 -8
- package/dist/callback-runner.js +0 -26
package/CHANGELOG.md
CHANGED
package/dist/index.js
CHANGED
|
@@ -59,11 +59,13 @@ 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
71
|
hooks.registerCommand({
|
|
@@ -197,24 +199,34 @@ async function getUser(ctx, runtime) {
|
|
|
197
199
|
}
|
|
198
200
|
const user = normalizeOAuthUserInfo(await response.json());
|
|
199
201
|
const latest = resolveOAuthStateForConfig(config, ctx.store.getState().oauth);
|
|
200
|
-
saveOAuthSession(ctx, config, { ...
|
|
202
|
+
saveOAuthSession(ctx, config, { ...latest, ...user });
|
|
201
203
|
return user;
|
|
202
204
|
}
|
|
203
205
|
async function logout(ctx) {
|
|
204
206
|
const config = parseOptionalOAuthConfig(ctx.store.getState().xfe);
|
|
205
207
|
const current = config ? resolveOAuthStateForConfig(config, ctx.store.getState().oauth) : undefined;
|
|
206
208
|
const name = config?.name || current?.name || DEFAULT_OAUTH_NAME;
|
|
207
|
-
const storeState =
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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 });
|
|
211
215
|
ctx.logger.success(`${name} 已退出登录`);
|
|
212
216
|
}
|
|
213
217
|
// ─── Config And State ──────────────────────────────────────
|
|
214
218
|
function resolveOAuthStateForConfig(config, state) {
|
|
215
219
|
const configKey = resolveOAuthConfigKey(config);
|
|
216
220
|
const session = state?.[configKey];
|
|
217
|
-
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
|
+
};
|
|
218
230
|
}
|
|
219
231
|
function parseOAuthConfig(xfeConfig) {
|
|
220
232
|
const config = parseOptionalOAuthConfig(xfeConfig);
|
|
@@ -235,8 +247,8 @@ function parseOptionalOAuthConfig(xfeConfig) {
|
|
|
235
247
|
}
|
|
236
248
|
function saveOAuthSession(ctx, config, session) {
|
|
237
249
|
const configKey = resolveOAuthConfigKey(config);
|
|
238
|
-
const storeState =
|
|
239
|
-
storeState[configKey] =
|
|
250
|
+
const storeState = { ...(ctx.store.getState().oauth ?? oauthStoreInitial) };
|
|
251
|
+
storeState[configKey] = session;
|
|
240
252
|
ctx.store.setState({ oauth: storeState });
|
|
241
253
|
}
|
|
242
254
|
function resolveOptionalOAuthState(xfeConfig, state) {
|
|
@@ -260,38 +272,8 @@ function resolveOAuthConfigKey(config) {
|
|
|
260
272
|
const digest = createHash(OAUTH_CONFIG_HASH_ALGORITHM).update(payload).digest(OAUTH_CONFIG_HASH_ENCODING);
|
|
261
273
|
return `${OAUTH_CONFIG_KEY_PREFIX}:${digest}`;
|
|
262
274
|
}
|
|
263
|
-
function cloneOAuthState(stateMap) {
|
|
264
|
-
if (!stateMap)
|
|
265
|
-
return {};
|
|
266
|
-
return Object.fromEntries(Object.entries(stateMap)
|
|
267
|
-
.filter((entry) => isOAuthSessionState(entry[1]))
|
|
268
|
-
.map(([configKey, session]) => [configKey, toOAuthSessionState(session, session.name)]));
|
|
269
|
-
}
|
|
270
|
-
function isOAuthSessionState(value) {
|
|
271
|
-
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
272
|
-
return false;
|
|
273
|
-
const record = value;
|
|
274
|
-
return typeof record.name === 'string';
|
|
275
|
-
}
|
|
276
|
-
function createEmptyOAuthSession(name) {
|
|
277
|
-
return toOAuthSessionState(undefined, name);
|
|
278
|
-
}
|
|
279
|
-
function toOAuthSessionState(state, name) {
|
|
280
|
-
return {
|
|
281
|
-
name: state?.name || name,
|
|
282
|
-
accessToken: state?.accessToken || '',
|
|
283
|
-
refreshToken: state?.refreshToken || '',
|
|
284
|
-
idToken: state?.idToken || '',
|
|
285
|
-
tokenType: state?.tokenType || DEFAULT_TOKEN_TYPE,
|
|
286
|
-
scope: state?.scope || '',
|
|
287
|
-
expiresAt: state?.expiresAt ?? 0,
|
|
288
|
-
isAuthenticated: state?.isAuthenticated ?? false,
|
|
289
|
-
userId: state?.userId ?? 0,
|
|
290
|
-
userName: state?.userName || '',
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
275
|
function isOAuthAccessTokenValid(state, now) {
|
|
294
|
-
return
|
|
276
|
+
return !!state.accessToken && (!state.expiresAt || state.expiresAt - TOKEN_EXPIRY_SKEW_SECONDS > now);
|
|
295
277
|
}
|
|
296
278
|
function createOAuthSessionFromToken(config, tokenResponse, now, fallbackRefreshToken = '') {
|
|
297
279
|
const expiresIn = tokenResponse.expiresIn();
|
|
@@ -299,11 +281,8 @@ function createOAuthSessionFromToken(config, tokenResponse, now, fallbackRefresh
|
|
|
299
281
|
name: config.name,
|
|
300
282
|
accessToken: tokenResponse.access_token ?? '',
|
|
301
283
|
refreshToken: tokenResponse.refresh_token || fallbackRefreshToken,
|
|
302
|
-
idToken: tokenResponse.id_token || '',
|
|
303
284
|
tokenType: tokenResponse.token_type || DEFAULT_TOKEN_TYPE,
|
|
304
|
-
scope: tokenResponse.scope || '',
|
|
305
285
|
expiresAt: expiresIn ? now + expiresIn : 0,
|
|
306
|
-
isAuthenticated: true,
|
|
307
286
|
userId: 0,
|
|
308
287
|
userName: '',
|
|
309
288
|
};
|
|
@@ -317,31 +296,16 @@ function normalizeOAuthUserInfo(input) {
|
|
|
317
296
|
const userRecord = nestedRecord ?? rootRecord;
|
|
318
297
|
const userIdKeys = ['userId', 'user_id', 'UserId', 'id', 'uid'];
|
|
319
298
|
const userNameKeys = ['name', 'userName', 'user_name', 'UserName', 'username', 'nickName', 'nikeName', 'nickname'];
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
if (typeof value === 'number' && Number.isInteger(value)) {
|
|
325
|
-
userId = value;
|
|
326
|
-
break;
|
|
327
|
-
}
|
|
328
|
-
if (typeof value === 'string' && value.trim()) {
|
|
329
|
-
const parsed = Number.parseInt(value, 10);
|
|
330
|
-
if (!Number.isNaN(parsed)) {
|
|
331
|
-
userId = parsed;
|
|
332
|
-
break;
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
}
|
|
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;
|
|
336
303
|
if (!userId)
|
|
337
304
|
throw new Error('OAuth 用户信息缺少 userId');
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
break;
|
|
343
|
-
}
|
|
344
|
-
}
|
|
305
|
+
const userName = userNameKeys
|
|
306
|
+
.map((key) => userRecord[key])
|
|
307
|
+
.find((value) => typeof value === 'string' && !!value.trim())
|
|
308
|
+
?.trim();
|
|
345
309
|
if (!userName)
|
|
346
310
|
throw new Error('OAuth 用户信息缺少 userName');
|
|
347
311
|
return { userId, userName };
|
package/dist/types.d.ts
CHANGED
|
@@ -24,11 +24,8 @@ 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
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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",
|
|
@@ -13,8 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
15
|
"@types/node": "^24.3.0",
|
|
16
|
-
"typescript": "^5.9.2"
|
|
17
|
-
"vitest": "^3.1.0"
|
|
16
|
+
"typescript": "^5.9.2"
|
|
18
17
|
},
|
|
19
18
|
"files": [
|
|
20
19
|
"dist",
|
|
@@ -24,8 +23,7 @@
|
|
|
24
23
|
"license": "ISC",
|
|
25
24
|
"scripts": {
|
|
26
25
|
"dev": "tsc -p tsconfig.json -w",
|
|
27
|
-
"build": "tsc -p tsconfig.json",
|
|
28
|
-
"test": "vitest run",
|
|
26
|
+
"build": "pnpm clean && tsc -p tsconfig.json",
|
|
29
27
|
"clean": "rm -rf dist"
|
|
30
28
|
}
|
|
31
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
|