You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by ra...@apache.org on 2026/05/18 06:10:56 UTC
(airflow) branch backport-322-66690 created (now 0764c18575a)
This is an automated email from the ASF dual-hosted git repository.
vatsrahul1001 pushed a change to branch backport-322-66690
in repository https://gitbox.apache.org/repos/asf/airflow.git
at 0764c18575a UI: Preserve proxied URL on login redirect (#66690)
This branch includes the following new commits:
new 0764c18575a UI: Preserve proxied URL on login redirect (#66690)
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(airflow) 01/01: UI: Preserve proxied URL on login redirect (#66690)
Posted by ra...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
vatsrahul1001 pushed a commit to branch backport-322-66690
in repository https://gitbox.apache.org/repos/asf/airflow.git
commit 0764c18575a2357e3d3f18db5847eaaaeec92086
Author: Sai Teja Desu <31...@users.noreply.github.com>
AuthorDate: Wed May 13 07:40:04 2026 -0700
UI: Preserve proxied URL on login redirect (#66690)
When the UI is reached through a proxy (Gitpod, Codespaces, ngrok,
reverse proxies), the auth-failure interceptor sent the API server's
absolute URL as the 'next' parameter, so post-login redirects went to
e.g. http://localhost:29091 instead of the URL the browser is on.
Send a same-origin path+search+hash instead, so the browser stays on
whatever origin it is currently using.
Add regression coverage for proxied subpaths so redirects also preserve
base paths such as /team-a/.
closes: #46533
(cherry picked from commit 25ef835185cb268a800ce117ecb88c6eea81ce77)
---
airflow-core/src/airflow/ui/src/main.tsx | 4 +-
.../src/airflow/ui/src/utils/links.test.ts | 52 +++++++++++++++++++++-
airflow-core/src/airflow/ui/src/utils/links.ts | 7 +++
3 files changed, 60 insertions(+), 3 deletions(-)
diff --git a/airflow-core/src/airflow/ui/src/main.tsx b/airflow-core/src/airflow/ui/src/main.tsx
index 9fb55690e4c..4daeb12b19e 100644
--- a/airflow-core/src/airflow/ui/src/main.tsx
+++ b/airflow-core/src/airflow/ui/src/main.tsx
@@ -34,7 +34,7 @@ import { ChakraCustomProvider } from "src/context/ChakraCustomProvider";
import { ColorModeProvider } from "src/context/colorMode";
import { TimezoneProvider } from "src/context/timezone";
import { router } from "src/router";
-import { getRedirectPath } from "src/utils/links.ts";
+import { getNextHref, getRedirectPath } from "src/utils/links.ts";
import i18n from "./i18n/config";
import { client } from "./queryClient";
@@ -75,7 +75,7 @@ axios.interceptors.response.use(
) {
const params = new URLSearchParams();
- params.set("next", globalThis.location.href);
+ params.set("next", getNextHref(globalThis.location));
const loginPath = getRedirectPath("api/v2/auth/login");
globalThis.location.replace(`${loginPath}?${params.toString()}`);
diff --git a/airflow-core/src/airflow/ui/src/utils/links.test.ts b/airflow-core/src/airflow/ui/src/utils/links.test.ts
index 542f508a962..2dc176258c6 100644
--- a/airflow-core/src/airflow/ui/src/utils/links.test.ts
+++ b/airflow-core/src/airflow/ui/src/utils/links.test.ts
@@ -1,3 +1,5 @@
+/* eslint-disable max-lines */
+
/*!
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
@@ -20,7 +22,12 @@ import { describe, it, expect } from "vitest";
import type { TaskInstanceResponse } from "openapi/requests/types.gen";
-import { buildTaskInstanceUrl, getTaskInstanceAdditionalPath, getTaskInstanceLink } from "./links";
+import {
+ buildTaskInstanceUrl,
+ getNextHref,
+ getTaskInstanceAdditionalPath,
+ getTaskInstanceLink,
+} from "./links";
describe("getTaskInstanceLink", () => {
const testCases = [
@@ -284,3 +291,46 @@ describe("buildTaskInstanceUrl", () => {
).toBe("/dags/new_dag/runs/new_run/tasks/new_task/mapped");
});
});
+
+describe("getNextHref", () => {
+ // Regression tests for https://github.com/apache/airflow/issues/46533 — the
+ // "next" parameter sent to the login redirect must be a same-origin relative
+ // URL so that proxied deployments (e.g. Gitpod) don't bounce the browser
+ // back to the API server's reported origin (e.g. http://localhost:29091).
+ it.each([
+ {
+ description: "preserves pathname only",
+ expected: "/dags/my_dag",
+ input: { hash: "", pathname: "/dags/my_dag", search: "" },
+ },
+ {
+ description: "preserves pathname and search",
+ expected: "/dags/my_dag?tab=graph",
+ input: { hash: "", pathname: "/dags/my_dag", search: "?tab=graph" },
+ },
+ {
+ description: "preserves pathname, search, and hash",
+ expected: "/dags/my_dag?tab=graph#section",
+ input: { hash: "#section", pathname: "/dags/my_dag", search: "?tab=graph" },
+ },
+ {
+ description: "preserves proxied base path, search, and hash",
+ expected: "/team-a/dags/my_dag?tab=graph#section",
+ input: { hash: "#section", pathname: "/team-a/dags/my_dag", search: "?tab=graph" },
+ },
+ {
+ description: "handles root path",
+ expected: "/",
+ input: { hash: "", pathname: "/", search: "" },
+ },
+ ])("$description", ({ expected, input }) => {
+ expect(getNextHref(input)).toBe(expected);
+ });
+
+ it("does not include the origin (no http(s) prefix)", () => {
+ const result = getNextHref({ hash: "", pathname: "/dags/my_dag", search: "" });
+
+ expect(result.startsWith("http://")).toBe(false);
+ expect(result.startsWith("https://")).toBe(false);
+ });
+});
diff --git a/airflow-core/src/airflow/ui/src/utils/links.ts b/airflow-core/src/airflow/ui/src/utils/links.ts
index 6770f2e7cbb..522a72fcdda 100644
--- a/airflow-core/src/airflow/ui/src/utils/links.ts
+++ b/airflow-core/src/airflow/ui/src/utils/links.ts
@@ -47,6 +47,13 @@ export const getRedirectPath = (targetPath: string): string => {
return new URL(targetPath, baseUrl).pathname;
};
+// Build a same-origin "next" target (path + query + hash) from a Location.
+// Using a relative URL ensures redirects work correctly when the UI is
+// reached through a proxy or a different origin than the API server reports
+// (e.g. Gitpod port-based domains, see #46533).
+export const getNextHref = (location: Pick<Location, "hash" | "pathname" | "search">): string =>
+ `${location.pathname}${location.search}${location.hash}`;
+
export const getTaskInstanceAdditionalPath = (pathname: string): string => {
const subRoutes = taskInstanceRoutes.filter((route) => route.path !== undefined).map((route) => route.path);
// Look for patterns like /tasks/{taskId}/mapped/{mapIndex}/{sub-route}