From 1847eb9d6a894aa4006a8d968d38207b55f45de9 Mon Sep 17 00:00:00 2001 From: fengxiangdebaozi <10845145+fengxiangdebaozi@user.noreply.gitee.com> Date: Sat, 27 Jun 2026 17:29:52 +0800 Subject: [PATCH] fix(auth): normalize external accessToken and preserve on empty yUser login --- .../src/core/auth/external-access-token.ts | 7 +++ frontend-web/src/core/auth/yuser-login.ts | 55 +++++++++++++------ .../src/core/strategy/external-links.ts | 4 +- .../KnowledgeBaseResourceManagement.tsx | 3 +- .../src/strategy-components/lib/light-app.ts | 4 ++ 5 files changed, 53 insertions(+), 20 deletions(-) create mode 100644 frontend-web/src/core/auth/external-access-token.ts diff --git a/frontend-web/src/core/auth/external-access-token.ts b/frontend-web/src/core/auth/external-access-token.ts new file mode 100644 index 00000000..27356aa8 --- /dev/null +++ b/frontend-web/src/core/auth/external-access-token.ts @@ -0,0 +1,7 @@ +export function normalizeExternalAccessToken(value: unknown): string { + if (value == null) return ""; + const token = String(value).trim(); + if (!token) return ""; + const lowered = token.toLowerCase(); + return lowered === "null" || lowered === "undefined" ? "" : token; +} diff --git a/frontend-web/src/core/auth/yuser-login.ts b/frontend-web/src/core/auth/yuser-login.ts index 21936506..e5217ece 100644 --- a/frontend-web/src/core/auth/yuser-login.ts +++ b/frontend-web/src/core/auth/yuser-login.ts @@ -1,5 +1,6 @@ import { requestCrudService } from "@/service/request-axios-service"; import { authStorageGet, authStorageSet } from "@/core/auth/scoped-storage"; +import { normalizeExternalAccessToken } from "@/core/auth/external-access-token"; const CONSUMER_URL = import.meta.env.VITE_AIPI_CONSUMER_URL || ""; @@ -15,6 +16,24 @@ type UserDetailResponse = { }; }; +function readExternalUserInfo(): Record { + try { + const raw = authStorageGet(EXTERNAL_USER_INFO_STORAGE_KEY); + if (!raw) return {}; + const parsed = JSON.parse(raw); + return parsed && typeof parsed === "object" && !Array.isArray(parsed) + ? (parsed as Record) + : {}; + } catch { + return {}; + } +} + +function readStoredYUserId(info: Record): string { + const value = info.yUserId; + return value == null ? "" : String(value).trim(); +} + /** * External login via yUserId + optional token (same flow as web-code Login.tsx): * persist tools_token, resolve user via consumer API, store userId/userInfo. @@ -38,10 +57,23 @@ export async function loginByYUserId( // 注意:接口也会返回 accessToken,但 XK/PG 及 source=accessToken 要用路由透传的 // access_token,故登录流程随后会用 persistAccessToken 覆盖 userInfo.accessToken。 const result = res.data ?? {}; - authStorageSet( - EXTERNAL_USER_INFO_STORAGE_KEY, - JSON.stringify({ ...result, yUserId }), - ); + const previous = readExternalUserInfo(); + const next: Record = { ...result, yUserId }; + const incomingAccessToken = normalizeExternalAccessToken(result.accessToken); + if (incomingAccessToken) { + next.accessToken = incomingAccessToken; + } else { + const previousAccessToken = normalizeExternalAccessToken(previous.accessToken); + const previousYUserId = readStoredYUserId(previous); + // Do not let an unstable upstream empty/null token wipe a usable token for + // the same external user. Avoid carrying it across different yUserId values. + if (previousAccessToken && previousYUserId === yUserId) { + next.accessToken = previousAccessToken; + } else { + delete next.accessToken; + } + } + authStorageSet(EXTERNAL_USER_INFO_STORAGE_KEY, JSON.stringify(next)); } export function persistToolsToken(token: string): void { @@ -56,20 +88,9 @@ export function persistToolsToken(token: string): void { * 用合并而非覆盖整对象,避免抹掉 loginByYUserId 从 consumer 拿到的用户信息。 */ export function persistAccessToken(token: string): void { - const trimmed = token?.trim(); + const trimmed = normalizeExternalAccessToken(token); if (!trimmed) return; - let info: Record = {}; - try { - const raw = authStorageGet(EXTERNAL_USER_INFO_STORAGE_KEY); - if (raw) { - const parsed = JSON.parse(raw); - if (parsed && typeof parsed === "object") { - info = parsed as Record; - } - } - } catch { - info = {}; - } + const info = readExternalUserInfo(); info.accessToken = trimmed; authStorageSet(EXTERNAL_USER_INFO_STORAGE_KEY, JSON.stringify(info)); } diff --git a/frontend-web/src/core/strategy/external-links.ts b/frontend-web/src/core/strategy/external-links.ts index 7d890d0d..bed25bc1 100644 --- a/frontend-web/src/core/strategy/external-links.ts +++ b/frontend-web/src/core/strategy/external-links.ts @@ -1,6 +1,7 @@ /** Strategy 模块外部 iframe / 嵌入页链接(来自 Vite 环境变量) */ import { authStorageGet } from "@/core/auth/scoped-storage"; +import { normalizeExternalAccessToken } from "@/core/auth/external-access-token"; function trimTrailingSlash(url: string): string { return url.replace(/\/+$/, ""); @@ -43,8 +44,7 @@ function readUserInfo(): Record { /** 当前登录态 accessToken(无则空串)。 */ export function getAccessToken(): string { - const value = readUserInfo().accessToken; - return value == null ? "" : String(value); + return normalizeExternalAccessToken(readUserInfo().accessToken); } /** diff --git a/frontend-web/src/strategy-components/components/resource-management/KnowledgeBaseResourceManagement.tsx b/frontend-web/src/strategy-components/components/resource-management/KnowledgeBaseResourceManagement.tsx index 305edc6e..69980b75 100644 --- a/frontend-web/src/strategy-components/components/resource-management/KnowledgeBaseResourceManagement.tsx +++ b/frontend-web/src/strategy-components/components/resource-management/KnowledgeBaseResourceManagement.tsx @@ -5,6 +5,7 @@ import { getKnowledgeIframeSkin, } from "@/core/strategy/external-links"; import { authStorageGet } from "@/core/auth/scoped-storage"; +import { normalizeExternalAccessToken } from "@/core/auth/external-access-token"; import { requestCrudService } from "@/service/request-axios-service"; import ExternalIframePage from "../common/ExternalIframePage"; @@ -24,7 +25,7 @@ const KnowledgeBaseResourceManagement: React.FC = () => { try { const userInfo = JSON.parse(jsonStr); - const token = userInfo.accessToken; + const token = normalizeExternalAccessToken(userInfo?.accessToken); if (!token) return; const res = (await service.post(apiUrl, { diff --git a/frontend-web/src/strategy-components/lib/light-app.ts b/frontend-web/src/strategy-components/lib/light-app.ts index 52694549..bde56c29 100644 --- a/frontend-web/src/strategy-components/lib/light-app.ts +++ b/frontend-web/src/strategy-components/lib/light-app.ts @@ -7,6 +7,7 @@ import type { LightApp, LightAppRouteParam } from "@/strategy-components/api/light-apps"; import { authStorageGet } from "@/core/auth/scoped-storage"; +import { normalizeExternalAccessToken } from "@/core/auth/external-access-token"; /** iframe 可挂载的父菜单(取自 core/page-layout/sidebar-menu.ts 的现有菜单 id)。 */ export const MOUNT_OPTIONS: { label: string; value: string }[] = [ @@ -142,6 +143,9 @@ export function resolveRouteParamValue( } if (param.source) { const value = userInfo[param.source]; + if (param.source === "accessToken") { + return normalizeExternalAccessToken(value); + } return value == null ? "" : String(value); } return "";