Fix SSO Proxy Base URL input validation and remove normalizing / (#16332)

This commit is contained in:
yuneng-jiang
2025-11-06 19:24:05 -08:00
committed by GitHub
parent 83998d3573
commit 29e8d857f7
2 changed files with 134 additions and 10 deletions
@@ -96,7 +96,7 @@ describe("SSOModals", () => {
});
});
it("should automatically remove trailing slash from the proxy base url", async () => {
it("should show validation error if the proxy base url ends with a trailing slash", async () => {
const TestWrapper = () => {
const [form] = Form.useForm();
return (
@@ -115,18 +115,137 @@ describe("SSOModals", () => {
);
};
const { getByLabelText, container } = render(<TestWrapper />);
const { getByLabelText, getByText, container } = render(<TestWrapper />);
// Fill in the proxy base url with a trailing slash
// Find and interact with the SSO provider select
const ssoProviderSelect = container.querySelector("#sso_provider");
if (ssoProviderSelect) {
fireEvent.mouseDown(ssoProviderSelect);
// Wait for dropdown and select Google
await waitFor(() => {
const googleOption = getByText("Google SSO");
fireEvent.click(googleOption);
});
}
// Fill in the email field
const emailInput = getByLabelText("Proxy Admin Email");
fireEvent.change(emailInput, { target: { value: "test@example.com" } });
// Fill in a URL with trailing slash
const urlInput = getByLabelText("PROXY BASE URL") as HTMLInputElement;
fireEvent.change(urlInput, { target: { value: "https://example.com/" } });
// Trigger blur to ensure normalization is applied
fireEvent.blur(urlInput);
// Submit the form
const saveButton = getByText("Save");
fireEvent.click(saveButton);
// Check that the trailing slash was removed by the normalize function
// Check for validation error
await waitFor(() => {
expect(urlInput.value).toBe("https://example.com");
expect(getByText("URL must not end with a trailing slash")).toBeInTheDocument();
});
});
it("should allow typing https:// without interfering with slashes", async () => {
const TestWrapper = () => {
const [form] = Form.useForm();
return (
<SSOModals
isAddSSOModalVisible={true}
isInstructionsModalVisible={false}
handleAddSSOOk={() => {}}
handleAddSSOCancel={() => {}}
handleShowInstructions={() => {}}
handleInstructionsOk={() => {}}
handleInstructionsCancel={() => {}}
form={form}
accessToken={null}
ssoConfigured={false}
/>
);
};
const { getByLabelText } = render(<TestWrapper />);
const urlInput = getByLabelText("PROXY BASE URL") as HTMLInputElement;
// Simulate user typing "https://"
fireEvent.change(urlInput, { target: { value: "h" } });
expect(urlInput.value).toBe("h");
fireEvent.change(urlInput, { target: { value: "ht" } });
expect(urlInput.value).toBe("ht");
fireEvent.change(urlInput, { target: { value: "http" } });
expect(urlInput.value).toBe("http");
fireEvent.change(urlInput, { target: { value: "https" } });
expect(urlInput.value).toBe("https");
fireEvent.change(urlInput, { target: { value: "https:" } });
expect(urlInput.value).toBe("https:");
fireEvent.change(urlInput, { target: { value: "https:/" } });
expect(urlInput.value).toBe("https:/");
fireEvent.change(urlInput, { target: { value: "https://" } });
expect(urlInput.value).toBe("https://");
// Continue typing the domain
fireEvent.change(urlInput, { target: { value: "https://example.com" } });
expect(urlInput.value).toBe("https://example.com");
});
it("should only show URL format error for incomplete URLs, not trailing slash error", async () => {
const TestWrapper = () => {
const [form] = Form.useForm();
return (
<SSOModals
isAddSSOModalVisible={true}
isInstructionsModalVisible={false}
handleAddSSOOk={() => {}}
handleAddSSOCancel={() => {}}
handleShowInstructions={() => {}}
handleInstructionsOk={() => {}}
handleInstructionsCancel={() => {}}
form={form}
accessToken={null}
ssoConfigured={false}
/>
);
};
const { getByLabelText, getByText, queryByText, container } = render(<TestWrapper />);
// Find and interact with the SSO provider select
const ssoProviderSelect = container.querySelector("#sso_provider");
if (ssoProviderSelect) {
fireEvent.mouseDown(ssoProviderSelect);
// Wait for dropdown and select Google
await waitFor(() => {
const googleOption = getByText("Google SSO");
fireEvent.click(googleOption);
});
}
// Fill in the email field
const emailInput = getByLabelText("Proxy Admin Email");
fireEvent.change(emailInput, { target: { value: "test@example.com" } });
// Fill in an incomplete URL like "http:"
const urlInput = getByLabelText("PROXY BASE URL");
fireEvent.change(urlInput, { target: { value: "http:" } });
// Submit the form
const saveButton = getByText("Save");
fireEvent.click(saveButton);
// Check that only the URL format error appears
await waitFor(() => {
expect(getByText("URL must start with http:// or https://")).toBeInTheDocument();
});
// Verify the trailing slash error does NOT appear
expect(queryByText("URL must not end with a trailing slash")).not.toBeInTheDocument();
});
});
@@ -309,7 +309,7 @@ const SSOModals: React.FC<SSOModalsProps> = ({
<Form.Item
label="PROXY BASE URL"
name="proxy_base_url"
normalize={(value) => value?.trim().replace(/\/+$/, "")}
normalize={(value) => value?.trim()}
rules={[
{ required: true, message: "Please enter the proxy base url" },
{
@@ -317,8 +317,13 @@ const SSOModals: React.FC<SSOModalsProps> = ({
message: "URL must start with http:// or https://",
},
{
pattern: /^https?:\/\/[^\s]+[^\/]$/,
message: "URL must not end with a trailing slash",
validator: (_, value) => {
// Only check for trailing slash if the URL starts with http:// or https://
if (value && /^https?:\/\/.+/.test(value) && value.endsWith("/")) {
return Promise.reject("URL must not end with a trailing slash");
}
return Promise.resolve();
},
},
]}
>