diff --git a/frontend-web/src/pages/LoginPage.tsx b/frontend-web/src/pages/LoginPage.tsx index 0662c5bd..5582c117 100644 --- a/frontend-web/src/pages/LoginPage.tsx +++ b/frontend-web/src/pages/LoginPage.tsx @@ -87,11 +87,30 @@ export default function LoginPage() { // 优先级最高:显式指定即按它跳,先于 taskId/goPath 深链与默认首页。出于安全只接受**站内 // 相对路径**:拒绝带协议的绝对 URL(http(s):、javascript: 等)、协议相对 URL(//host)与反斜杠, // 避免开放式跳转;缺前导斜杠时自动补全。 + // + // 参数透传:登录 URL 上「非登录自身消费」的其余 query(如 taskId / hideChrome)会被透传到 + // 目标页 query——这样 `?redirect=/page/strategy/dashboard-agent&taskId=3334` 也能让目标页拿到 + // taskId,无需把它编进 redirect 值里。目标已自带的同名参数优先(不覆盖);登录鉴权类参数 + // (token/authToken/ticket 等)一律**不透传**,避免泄漏到应用页地址栏。 const redirectTo = useMemo(() => { const raw = searchParams.get('redirect')?.trim() ?? ''; if (!raw) return ''; if (/^[a-z][a-z0-9+.-]*:/i.test(raw) || raw.startsWith('//') || raw.includes('\\')) return ''; - return raw.startsWith('/') ? raw : `/${raw}`; + const normalized = raw.startsWith('/') ? raw : `/${raw}`; + const [pathOnly, ownQuery = ''] = normalized.split('?'); + const target = new URLSearchParams(ownQuery); + // 登录流程自身消费的参数,不透传到目标页(尤以鉴权类为甚)。 + const LOGIN_OWNED = new Set([ + 'redirect', 'ticket', 'userId', 'env', 'theme', 'yUserId', + 'token', 'access_token', 'password', 'authToken', 'goPath', 'from', + ]); + new URLSearchParams(searchParams.toString()).forEach((value, key) => { + if (LOGIN_OWNED.has(key)) return; + if (target.has(key)) return; // 目标自带优先 + target.set(key, value); + }); + const qs = target.toString(); + return qs ? `${pathOnly}?${qs}` : pathOnly; }, [searchParams]); const externalParams = useMemo( () => ({