fix(web): validate IdP issuer URL inline in Client Settings (#1533) (#1534)

The Enterprise-Managed Authorization "Issuer" field accepted arbitrary
non-URL text and only failed at save time, surfacing a raw Zod issue array
in "Failed to save client settings" toasts.

- Inline field-level validation: the Issuer input shows a friendly error
  ("Must be an http(s) URL, like https://idp.example.com").
- canPersistClientSettingsDraft gates on issuer validity (deferring to
  validateClientSettings) so an invalid value is never sent to the backend.
- Extract isAbsoluteHttpUrl in core/client/config-parse so the Zod schema
  and the form share one http(s)-only URL rule.
- Harden the fallback save-error toast via formatClientConfigLoadError.

Closes #1533

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cliff Hall 2026-06-27 16:08:22 -04:00 • committed by GitHub
commit 1dc0c744cf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -17,7 +17,10 @@ import {
parseClientConfig,
saveClientConfig,
} from "@inspector/core/client/config.js";
import { formatClientConfigLoadError } from "@inspector/core/client/config-parse.js";
import {
formatClientConfigLoadError,
isAbsoluteHttpUrl,
} from "@inspector/core/client/config-parse.js";
import {
getActiveEnterpriseManagedAuthIdp,
isEnterpriseManagedAuthEnabled,
@ -87,6 +90,31 @@ describe("client config", () => {
).toThrow();
});
it("isAbsoluteHttpUrl accepts http(s) URLs and trims, rejects others", () => {
expect(isAbsoluteHttpUrl("https://idp.example.com")).toBe(true);
expect(isAbsoluteHttpUrl("http://localhost:6274")).toBe(true);
expect(isAbsoluteHttpUrl(" https://idp.example.com ")).toBe(true);
expect(isAbsoluteHttpUrl("not-a-url")).toBe(false);
expect(isAbsoluteHttpUrl("")).toBe(false);
});
it("isAbsoluteHttpUrl rejects non-http(s) schemes", () => {
expect(isAbsoluteHttpUrl("foo:bar")).toBe(false);
expect(isAbsoluteHttpUrl("mailto:a@b.com")).toBe(false);
expect(isAbsoluteHttpUrl("ftp://idp.example.com")).toBe(false);
expect(isAbsoluteHttpUrl("javascript:void(0)")).toBe(false);
});
it("parseClientConfig rejects a non-http(s) issuer scheme", () => {
expect(() =>
parseClientConfig({
enterpriseManagedAuth: {
idp: { issuer: "mailto:idp@example.com", clientId: "c" },
},
}),
).toThrow(/Invalid URL/);
});
it("loadClientConfig returns {} when file is absent", async () => {
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "client-config-"));
const filePath = path.join(tmpDir, "client.json");