mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-03-22 13:47:19 +08:00
fix: preserve query params in returnTo during login redirect (#12567)
Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
@@ -48,6 +48,7 @@ function LoginStub() {
|
||||
searchParams.get("email_verification_required") === "true";
|
||||
const emailVerified = searchParams.get("email_verified") === "true";
|
||||
const emailVerificationText = "AUTH$PLEASE_CHECK_EMAIL_TO_VERIFY";
|
||||
const returnTo = searchParams.get("returnTo");
|
||||
|
||||
return (
|
||||
<div data-testid="login-page">
|
||||
@@ -58,6 +59,7 @@ function LoginStub() {
|
||||
{emailVerificationText}
|
||||
</div>
|
||||
)}
|
||||
{returnTo && <div data-testid="return-to-param">{returnTo}</div>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -100,6 +102,27 @@ const RouterStubWithLogin = createRoutesStub([
|
||||
},
|
||||
]);
|
||||
|
||||
const RouterStubWithDeviceVerify = createRoutesStub([
|
||||
{
|
||||
Component: MainApp,
|
||||
path: "/",
|
||||
children: [
|
||||
{
|
||||
Component: () => <div data-testid="outlet-content" />,
|
||||
path: "/",
|
||||
},
|
||||
{
|
||||
Component: () => <div data-testid="device-verify-page" />,
|
||||
path: "/oauth/device/verify",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
Component: LoginStub,
|
||||
path: "/login",
|
||||
},
|
||||
]);
|
||||
|
||||
const renderMainApp = (initialEntries: string[] = ["/"]) =>
|
||||
render(<RouterStub initialEntries={initialEntries} />, {
|
||||
wrapper: ({ children }) => (
|
||||
@@ -311,5 +334,23 @@ describe("MainApp", () => {
|
||||
{ timeout: 2000 },
|
||||
);
|
||||
});
|
||||
|
||||
it("should preserve query parameters in returnTo when redirecting to login", async () => {
|
||||
renderWithLoginStub(RouterStubWithDeviceVerify, [
|
||||
"/oauth/device/verify?user_code=F9XN6BKU",
|
||||
]);
|
||||
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(screen.getByTestId("login-page")).toBeInTheDocument();
|
||||
const returnToElement = screen.getByTestId("return-to-param");
|
||||
expect(returnToElement).toBeInTheDocument();
|
||||
expect(returnToElement.textContent).toBe(
|
||||
"/oauth/device/verify?user_code=F9XN6BKU",
|
||||
);
|
||||
},
|
||||
{ timeout: 2000 },
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
Outlet,
|
||||
useNavigate,
|
||||
useLocation,
|
||||
useSearchParams,
|
||||
} from "react-router";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
@@ -67,6 +68,7 @@ export default function MainApp() {
|
||||
const appTitle = useAppTitle();
|
||||
const navigate = useNavigate();
|
||||
const { pathname } = useLocation();
|
||||
const [searchParams] = useSearchParams();
|
||||
const isOnTosPage = useIsOnTosPage();
|
||||
const { data: settings } = useSettings();
|
||||
const { migrateUserConsent } = useMigrateUserConsent();
|
||||
@@ -182,13 +184,18 @@ export default function MainApp() {
|
||||
|
||||
React.useEffect(() => {
|
||||
if (shouldRedirectToLogin) {
|
||||
const returnTo = pathname !== "/" ? pathname : "";
|
||||
const loginUrl = returnTo
|
||||
? `/login?returnTo=${encodeURIComponent(returnTo)}`
|
||||
// Include search params in returnTo to preserve query string (e.g., user_code for device OAuth)
|
||||
const searchString = searchParams.toString();
|
||||
let fullPath = "";
|
||||
if (pathname !== "/") {
|
||||
fullPath = searchString ? `${pathname}?${searchString}` : pathname;
|
||||
}
|
||||
const loginUrl = fullPath
|
||||
? `/login?returnTo=${encodeURIComponent(fullPath)}`
|
||||
: "/login";
|
||||
navigate(loginUrl, { replace: true });
|
||||
}
|
||||
}, [shouldRedirectToLogin, pathname, navigate]);
|
||||
}, [shouldRedirectToLogin, pathname, searchParams, navigate]);
|
||||
|
||||
if (shouldRedirectToLogin) {
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user