fix(登录): token 登录补全 userInfo + 全方式打印路由参数

- 把「拉外部用户信息(yUserId→consumer)」与登录鉴权方式解耦:只要 URL 带
  yUserId 就填充 userInfo(含 token 登录,consumer 失败非致命),修复 token
  登录后 userInfo 残缺、iframe/轻应用拿不到 yUserId/userName/gateDepartName/
  typeName/accessToken 的问题。
- token 登录用 auth.username 写入正确用户名(不覆盖 consumer 已落的 userName)。
- 保留 yUserId 优先级(onlineTicket/toolToken 仅在无 yUserId 时启用)。
- 新增登录路由参数 + 落地结果日志(令牌脱敏),方便排查各登录方式取值问题。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
董宏凯andCursor committed 2026-06-29 23:36:33 +08:00
1 parent 6378cd33bd
commit 537e2e4d06
1 file changed
+93 -4
+93 -4
View File
@@ -35,6 +35,35 @@ import { fetchYLogConfig } from '@/core/log/y-log-config';
import { persistYLogToken } from '@/core/log/y-log-token';
import { persistLoginCredentials } from '@/core/auth/login-credentials';
import { OptType, writeYLog } from '@/core/log/y-log';
import { authStorageGet } from '@/core/auth/scoped-storage';
/** 令牌脱敏:日志里只露头尾,避免完整令牌泄漏。 */
function maskLoginToken(value: string): string {
if (!value) return '';
if (value.length <= 10) return `${value.slice(0, 2)}***(${value.length})`;
return `${value.slice(0, 6)}...${value.slice(-4)}(${value.length})`;
}
/** 读取已落地的 userInfo(脱敏 access token 后)供登录后排查。 */
function summarizeStoredUserInfo(): Record<string, unknown> | null {
try {
const raw = authStorageGet('userInfo');
if (!raw) return null;
const info = JSON.parse(raw) as Record<string, unknown>;
return {
keys: Object.keys(info),
yUserId: info.yUserId,
userId: info.userId,
userName: info.userName,
gateDepartName: info.gateDepartName,
typeName: info.typeName,
access_token: maskLoginToken(String(info.access_token ?? '')),
accessToken: maskLoginToken(String(info.accessToken ?? '')),
};
} catch {
return null;
}
}
const DEFAULT_USERNAME = 'guest';
const IFRAME_LOGIN_SOURCE = 'iframe';
@@ -171,19 +200,68 @@ export default function LoginPage() {
async function runLogin() {
setStatus('loading');
setErrorMessage('');
// ── 调试:登录路由参数全打印 ──────────────────────────────────────────────
// token / yUserId / 用户名 / ticket 任一方式都打印(令牌脱敏),方便排查
// “userInfo 取不到值 / 传值不对” 一类问题。`embed=0`/正常态也照打。
const loginMethod = authToken
? 'token(authToken)'
: yUserId
? 'yUserId'
: onlineTicket
? 'onlineTicket'
: toolToken
? 'toolsToken'
: 'username';
console.info('[login] route params', {
method: loginMethod,
pathUsername: resolvedUsername,
yUserId,
access_token: maskLoginToken(access_token),
toolToken: maskLoginToken(toolToken),
authToken: maskLoginToken(authToken),
gatePassword: gatePassword ? maskLoginToken(gatePassword) : '',
onlineTicket: onlineTicket ? maskLoginToken(onlineTicket) : '',
taskId,
goPath,
redirect: redirectTo,
isEmbed,
rawSearch: searchParams.toString(),
});
try {
let auth;
let loginUserName = '';
// 外部用户信息(yUserId → consumer 用户详情)与登录鉴权方式**解耦**:只要 URL
// 带 yUserId 就拉一次 userDetailByYuserId,把 yUserId/userName/gateDepartName/
// typeName/accessToken 落进 userInfo。之前 token(authToken) 登录会短路掉这一步,
// 导致 token 登录后 userInfo 残缺(只剩 userName)、iframe/轻应用拿不到这些值。
if (yUserId) {
if (authToken) {
// token 登录下 consumer 失败不应阻断登录(仅影响 userInfo 的富信息)。
try {
await loginByYUserId(yUserId, toolToken || null, access_token || null);
} catch (error) {
console.warn('[login] loginByYUserId failed under token login (non-fatal)', error);
}
} else {
await loginByYUserId(yUserId, toolToken || null, access_token || null);
}
}
if (authToken) {
// token 换登录:后端用 authToken 调上游接口换出用户名再登录
auth = await loginByToken(authToken);
// 让 token 登录也写入正确用户名(后端 getTokenInfo 解析出的原始登录名)。
// 若上面 yUserId 已从 consumer 落了 userName,则 setStoredUserName 不会覆盖它。
loginUserName = auth.username?.trim() ?? '';
} else {
let deerflowUsername = resolvedUsername;
if (yUserId) {
await loginByYUserId(yUserId, toolToken || null, access_token || null);
} else if (onlineTicket) {
// yUserId 优先(已在上面落好 userInfo + tools_token);其余方式仅在无 yUserId 时启用。
if (!yUserId && onlineTicket) {
deerflowUsername = await resolveUsernameByOnlineTicket(onlineTicket);
} else if (toolToken) {
} else if (!yUserId && toolToken) {
persistToolsToken(toolToken);
}
@@ -217,6 +295,17 @@ export default function LoginPage() {
if (!yUserId) {
persistAccessToken(access_token);
}
// ── 调试:登录落地结果(auth + 实际写入的 userInfo,access token 已脱敏) ──
console.info('[login] stored result', {
method: loginMethod,
user_id: auth.user_id,
email: auth.email,
authUsername: auth.username,
loginUserName,
userInfo: summarizeStoredUserInfo(),
});
// 嵌入模式不写 sidebarEnabled(localStorage 的「简洁/全量」偏好会跨标签污染正常页);
// 去 chrome 由按标签页的嵌入会话(isEmbedActive)驱动,见 PageLayout。
if (!isEmbed) persistSidebarEnabled(sidebarEnabled);