From 0895ebbdcb22ecb0fe1d3165a9f4d4efa6f0bc4b Mon Sep 17 00:00:00 2001 From: Cristhian Zanforlin Lousa Date: Mon, 23 Jun 2025 15:53:08 -0300 Subject: [PATCH] feat: Add conditional API key display for auth and auto-login (#8684) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨ Add IS_AUTO_LOGIN constant to handle auto login feature in NodeInputField and TableNodeCellRender components 🔧 Add loginLangflow utility function to facilitate login process in tests * ✨ (NodeInputField/index.tsx): introduce useIsAutoLogin hook to simplify logic for determining auto login status ✨ (tableNodeCellRender/index.tsx): introduce useIsAutoLogin hook to simplify logic for determining auto login status 📝 (use-is-auto-login.ts): add custom hook useIsAutoLogin to encapsulate logic for determining auto login status --- .../components/NodeInputField/index.tsx | 12 ++- .../components/tableNodeCellRender/index.tsx | 10 +- src/frontend/src/hooks/use-is-auto-login.ts | 8 ++ ...-component-webhook-api-key-display.spec.ts | 99 +++++++++++++++++++ src/frontend/tests/utils/login-langflow.ts | 8 ++ 5 files changed, 131 insertions(+), 6 deletions(-) create mode 100644 src/frontend/src/hooks/use-is-auto-login.ts create mode 100644 src/frontend/tests/extended/regression/general-bugs-component-webhook-api-key-display.spec.ts create mode 100644 src/frontend/tests/utils/login-langflow.ts diff --git a/src/frontend/src/CustomNodes/GenericNode/components/NodeInputField/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/NodeInputField/index.tsx index 9a337e228..a787ae546 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/NodeInputField/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/NodeInputField/index.tsx @@ -6,6 +6,7 @@ import { CustomParameterLabel, getCustomParameterTitle, } from "@/customization/components/custom-parameter"; +import { useIsAutoLogin } from "@/hooks/use-is-auto-login"; import useAuthStore from "@/stores/authStore"; import { cn } from "@/utils/utils"; import { useEffect, useMemo, useRef } from "react"; @@ -16,6 +17,7 @@ import { DEFAULT_TOOLSET_PLACEHOLDER, FLEX_VIEW_TYPES, ICON_STROKE_WIDTH, + IS_AUTO_LOGIN, LANGFLOW_SUPPORTED_TYPES, } from "../../../../constants/constants"; import useFlowStore from "../../../../stores/flowStore"; @@ -44,13 +46,17 @@ export default function NodeInputField({ isToolMode = false, }: NodeInputFieldComponentType): JSX.Element { const ref = useRef(null); - const isAuth = useAuthStore((state) => state.isAuthenticated); + const isAuthenticated = useAuthStore((state) => state.isAuthenticated); + const isAutoLogin = useIsAutoLogin(); + const shouldDisplayApiKey = isAuthenticated && !isAutoLogin; + const { currentFlowId, currentFlowName } = useFlowStore( useShallow((state) => ({ currentFlowId: state.currentFlow?.id, currentFlowName: state.currentFlow?.name, })), ); + const myData = useTypesStore((state) => state.data); const postTemplateValue = usePostTemplateValue({ node: data.node!, @@ -75,10 +81,10 @@ export default function NodeInputField({ flowId: currentFlowId ?? "", nodeType: data?.type?.toLowerCase() ?? "", flowName: currentFlowName ?? "", - isAuth, + isAuth: shouldDisplayApiKey!, variableName: name, }; - }, [data?.node?.id, isAuth, name]); + }, [data?.node?.id, shouldDisplayApiKey, name]); useFetchDataOnMount( data.node!, diff --git a/src/frontend/src/components/core/parameterRenderComponent/components/tableComponent/components/tableNodeCellRender/index.tsx b/src/frontend/src/components/core/parameterRenderComponent/components/tableComponent/components/tableNodeCellRender/index.tsx index a3133e9e5..fd637ab40 100644 --- a/src/frontend/src/components/core/parameterRenderComponent/components/tableComponent/components/tableNodeCellRender/index.tsx +++ b/src/frontend/src/components/core/parameterRenderComponent/components/tableComponent/components/tableNodeCellRender/index.tsx @@ -2,6 +2,8 @@ import useHandleOnNewValue from "@/CustomNodes/hooks/use-handle-new-value"; import useHandleNodeClass from "@/CustomNodes/hooks/use-handle-node-class"; import { ParameterRenderComponent } from "@/components/core/parameterRenderComponent"; import { NodeInfoType } from "@/components/core/parameterRenderComponent/types"; +import { IS_AUTO_LOGIN } from "@/constants/constants"; +import { useIsAutoLogin } from "@/hooks/use-is-auto-login"; import useAuthStore from "@/stores/authStore"; import useFlowStore from "@/stores/flowStore"; import { APIClassType } from "@/types/api"; @@ -17,7 +19,9 @@ export default function TableNodeCellRender({ const node = useFlowStore((state) => state.getNode(nodeId)); const parameter = node?.data?.node?.template?.[parameterId]; const currentFlow = useFlowStore((state) => state.currentFlow); - const isAuth = useAuthStore((state) => state.isAuthenticated); + const isAuthenticated = useAuthStore((state) => state.isAuthenticated); + const isAutoLogin = useIsAutoLogin(); + const shouldDisplayApiKey = isAuthenticated && !isAutoLogin; const disabled = isTargetHandleConnected( edges, @@ -43,10 +47,10 @@ export default function TableNodeCellRender({ flowId: currentFlow?.id ?? "", nodeType: node?.data?.type?.toLowerCase() ?? "", flowName: currentFlow?.name ?? "", - isAuth, + isAuth: shouldDisplayApiKey!, variableName: parameterId, }; - }, [nodeId, isAuth, parameterId]); + }, [nodeId, shouldDisplayApiKey, parameterId]); return ( parameter && ( diff --git a/src/frontend/src/hooks/use-is-auto-login.ts b/src/frontend/src/hooks/use-is-auto-login.ts new file mode 100644 index 000000000..82be4662f --- /dev/null +++ b/src/frontend/src/hooks/use-is-auto-login.ts @@ -0,0 +1,8 @@ +import { IS_AUTO_LOGIN } from "@/constants/constants"; +import useAuthStore from "@/stores/authStore"; + +export const useIsAutoLogin = (): boolean => { + const autoLogin = useAuthStore((state) => state.autoLogin); + const isAutoLoginEnv = IS_AUTO_LOGIN; + return autoLogin ?? isAutoLoginEnv; +}; diff --git a/src/frontend/tests/extended/regression/general-bugs-component-webhook-api-key-display.spec.ts b/src/frontend/tests/extended/regression/general-bugs-component-webhook-api-key-display.spec.ts new file mode 100644 index 000000000..34401a95b --- /dev/null +++ b/src/frontend/tests/extended/regression/general-bugs-component-webhook-api-key-display.spec.ts @@ -0,0 +1,99 @@ +import { expect, test } from "@playwright/test"; +import { adjustScreenView } from "../../utils/adjust-screen-view"; +import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { extractAndCleanCode } from "../../utils/extract-and-clean-code"; +import { loginLangflow } from "../../utils/login-langflow"; + +test( + "user must be able to see api key in webhook component when auto login is disabled", + { tag: ["@release"] }, + async ({ page }) => { + await page.route("**/api/v1/auto_login", (route) => { + route.fulfill({ + status: 500, + contentType: "application/json", + body: JSON.stringify({ + detail: { auto_login: false }, + }), + }); + }); + + await loginLangflow(page); + + await awaitBootstrapTest(page, { skipGoto: true }); + + await page.waitForSelector('[data-testid="blank-flow"]', { + timeout: 30000, + }); + await page.getByTestId("blank-flow").click(); + await page.getByTestId("sidebar-search-input").click(); + + await page.getByTestId("sidebar-search-input").fill("webhook"); + + await page.waitForSelector('[data-testid="dataWebhook"]', { + timeout: 3000, + }); + + await page + .getByTestId("dataWebhook") + .hover() + .then(async () => { + await page.getByTestId("add-component-button-webhook").click(); + }); + + await adjustScreenView(page); + + await page.getByTestId("title-Webhook").click(); + + await page.getByTestId("edit-button-modal").click(); + + await page + .getByTestId("button_open_text_area_modal_str_edit_curl_advanced") + .click(); + + const curl = await page.getByTestId("text-area-modal").inputValue(); + + expect(curl).toContain("x-api-key"); + }, +); + +test( + "user must be able to not see api key in webhook component when auto login is enabled", + { tag: ["@release"] }, + async ({ page }) => { + await awaitBootstrapTest(page); + + await page.waitForSelector('[data-testid="blank-flow"]', { + timeout: 30000, + }); + await page.getByTestId("blank-flow").click(); + await page.getByTestId("sidebar-search-input").click(); + + await page.getByTestId("sidebar-search-input").fill("webhook"); + + await page.waitForSelector('[data-testid="dataWebhook"]', { + timeout: 3000, + }); + + await page + .getByTestId("dataWebhook") + .hover() + .then(async () => { + await page.getByTestId("add-component-button-webhook").click(); + }); + + await adjustScreenView(page); + + await page.getByTestId("title-Webhook").click(); + + await page.getByTestId("edit-button-modal").click(); + + await page + .getByTestId("button_open_text_area_modal_str_edit_curl_advanced") + .click(); + + const curl = await page.getByTestId("text-area-modal").inputValue(); + + expect(curl).not.toContain("x-api-key"); + }, +); diff --git a/src/frontend/tests/utils/login-langflow.ts b/src/frontend/tests/utils/login-langflow.ts new file mode 100644 index 000000000..802180a9b --- /dev/null +++ b/src/frontend/tests/utils/login-langflow.ts @@ -0,0 +1,8 @@ +import { Page } from "playwright/test"; + +export const loginLangflow = async (page: Page) => { + await page.goto("/"); + await page.getByPlaceholder("Username").fill("langflow"); + await page.getByPlaceholder("Password").fill("langflow"); + await page.getByRole("button", { name: "Sign In" }).click(); +};