fix(auth): normalize external accessToken and preserve on empty yUser login

This commit is contained in:
fengxiangdebaozi committed 2026-06-27 17:29:52 +08:00
1 parent 44d7304f8e
commit 1847eb9d6a
5 files changed
+53 -20

No files matched your search

@@ -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;
}
+38 -17
View File
@@ -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<string, unknown> {
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<string, unknown>)
: {};
} catch {
return {};
}
}
function readStoredYUserId(info: Record<string, unknown>): 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<string, unknown> = { ...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<string, unknown> = {};
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<string, unknown>;
}
}
} catch {
info = {};
}
const info = readExternalUserInfo();
info.accessToken = trimmed;
authStorageSet(EXTERNAL_USER_INFO_STORAGE_KEY, JSON.stringify(info));
}
@@ -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<string, unknown> {
/** 当前登录态 accessToken(无则空串)。 */
export function getAccessToken(): string {
const value = readUserInfo().accessToken;
return value == null ? "" : String(value);
return normalizeExternalAccessToken(readUserInfo().accessToken);
}
/**
@@ -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, {
@@ -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 "";