Skip to content

fix(frontend): logout lands on /; 401 redirect preserves query safely - #774

Open
CH3CHO wants to merge 1 commit into
higress-group:mainfrom
CH3CHO:fix/logout-redirect-root
Open

fix(frontend): logout lands on /; 401 redirect preserves query safely#774
CH3CHO wants to merge 1 commit into
higress-group:mainfrom
CH3CHO:fix/logout-redirect-root

Conversation

@CH3CHO

@CH3CHO CH3CHO commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

Fixes #772

Problem

Clicking "logout" in the avatar dropdown navigates the user to /login?redirect=<pathname>. This has two defects:

  1. Query string is dropped. redirect uses pathname only, not pathname + search. On /users?tab=profile, after login the user lands on /users and loses tab=profile, breaking page state.
  2. No URL encoding. Special characters in the path are not escaped, leading to parsing errors.

User request: after logout, default to landing on the root page after re-login.

Solution

Extract two pure functions into frontend/src/utils/redirect.ts:

  • sanitizeRedirectValue(raw) — validates and sanitizes a redirect query value. Rejects //host, http(s)://, javascript:, the /login self-loop (case-insensitive, with or without query/hash suffix), /\host, and C0 control characters. All open-redirect defenses live here.
  • buildRedirectSearch(pathname, search) — used in the 401 flow. Combines pathname + search, runs encodeURIComponent over the result, and returns redirect=<encoded> (without the leading ?). The caller decides whether to add ?.

Three call sites share the helpers:

  • AvatarDropdown/index.tsx — active logout now calls history.push('/login'), no redirect param.
  • services/request.tsx — 401 interceptor uses buildRedirectSearch(pathname, search) so pathname + search round-trip cleanly.
  • pages/login/index.tsx — after successful login, sanitizeRedirectValue(urlParams.get('redirect')) validates the value before navigation.

Additional Security Hardening (from code review)

Bugs that code review caught before the fix landed:

  1. /\evil.com protocol-relative bypass. WHATWG URL parsing treats \ as / inside the authority, so /\evil.com resolves to //evil.com. Closed by adding raw[1] === '\' to the leading-slash guard.
  2. ASCII control-character bypass. C0 controls (TAB, LF, CR) are stripped by URL parsing before parsing, so /\t/evil.com, /\n/evil.com, /\r/evil.com all become //evil.com. Closed by adding /[\x00-\x1F\x7F]/ rejection.
  3. /login self-loop guard was exact-match. The original === check could be bypassed by /login?next=1, /LOGIN, etc. Tightened to a path-segment match.

Files Changed

File Change
frontend/src/utils/redirect.ts New. sanitizeRedirectValue + buildRedirectSearch
frontend/src/utils/redirect.test.ts New. 31 node:test cases
frontend/src/components/AvatarDropdown/index.tsx Drop redirect=<pathname> in doLogout
frontend/src/services/request.tsx Use buildRedirectSearch in 401 handler
frontend/src/pages/login/index.tsx Use sanitizeRedirectValue post-login

Verification

  • 31 node:test cases all green (covers normal paths, all known bypass vectors).
  • ESLint clean (eslint-disable-next-line suppressions are scoped to attack-vector test fixtures only).
  • tsc --noEmit introduces no new errors (only the pre-existing dompurify typing issue remains).
  • Backwards-compatible: legitimate paths still flow through unchanged; the login page's existing fallback to / is preserved.

Test Plan (manual e2e)

  • Login → visit /users?tab=profile → click logout → after re-login land on / (not /users)
  • Login → visit /users?tab=profile → clear localStorage.token and make any API call → after re-login return to /users?tab=profile
  • Open /login?redirect=https://example.com directly → login → land on /
  • Open /login?redirect=//example.com directly → login → land on /
  • Open /login?redirect=/login or /login?redirect=/LOGIN directly → login → land on /

- AvatarDropdown no longer passes redirect=<pathname>; login page falls back to /
- request.tsx 401 handler uses buildRedirectSearch so pathname+search round-trip
- login page uses sanitizeRedirectValue to block open-redirect (//, http(s):, javascript:, /login self-loop, /\, control chars)
- new frontend/src/utils/redirect.ts with sanitizeRedirectValue + buildRedirectSearch
- node:test coverage for both pure functions (31 cases)

Signed-off-by: CH3CHO <ch3cho@qq.com>
@CH3CHO
CH3CHO force-pushed the fix/logout-redirect-root branch from 07dd830 to e80b1ab Compare August 27, 2026 02:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

mcp管理界面,退出登录重进后报错502

1 participant