Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

- **fix(security):** the mutable cloud-agent routes (`/api/cloud/credentials/update`, `/api/cloud/models/alias`) now require management auth instead of being treated as public. They were classified as public API routes, so a request without management credentials could update stored cloud-agent credentials and model aliases. They are removed from the public-route set, classified as management routes in the authz pipeline, and gated by `requireManagementAuth`; cloud **read**/auth routes stay public. Regression guards: `tests/unit/cloud-write-auth.test.ts`, `tests/unit/authz/classify.test.ts`, `tests/unit/public-api-routes.test.ts`. ([#6233](https://github.com/diegosouzapw/OmniRoute/pull/6233) — thanks @vittoroliveira-dev)

- **refactor(dashboard):** extract the onboarding-wizard "Open provider details" link target into a pure, unit-tested `buildProviderDetailsHref(connection)` helper. The wizard already routes by `connection.id` (the node UUID) rather than the provider category slug (#6144/#6145); this hardens that behavior behind a tested helper that guards a missing id/connection. Regression guard: `tests/unit/provider-onboarding-href.test.ts`. ([#6166](https://github.com/diegosouzapw/OmniRoute/pull/6166) — thanks @KooshaPari)

---

## [3.8.43] — 2026-07-02
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
getWizardOAuthProviderOptions,
type WizardProviderOption,
} from "./providerOnboardingCatalog";
import { buildProviderDetailsHref } from "./providerOnboardingHref";
import {
createCompatibleProviderNode,
createOnboardingConnection,
Expand Down Expand Up @@ -260,14 +261,19 @@ function ResultSummary({
)}

<div className="flex flex-wrap gap-2">
{connection?.id && (
<Link
href={`/dashboard/providers/${connection.id}`}
className="inline-flex items-center justify-center rounded-lg bg-primary px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-primary/90"
>
{providerText(t, "onboardingOpenProviderDetails", "Open provider details")}
</Link>
)}
{(() => {
const detailsHref = buildProviderDetailsHref(connection);
return (
detailsHref && (
<Link
href={detailsHref}
className="inline-flex items-center justify-center rounded-lg bg-primary px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-primary/90"
>
{providerText(t, "onboardingOpenProviderDetails", "Open provider details")}
</Link>
)
);
})()}
Comment on lines +264 to +276

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using an Immediately Invoked Function Expression (IIFE) inside JSX is generally an anti-pattern in React as it introduces unnecessary complexity and nesting. Since buildProviderDetailsHref is a lightweight, pure function, we can directly use a logical AND (&&) operator and a non-null assertion (!) to render the link cleanly without the IIFE wrapper.

          {buildProviderDetailsHref(connection) && (
            <Link
              href={buildProviderDetailsHref(connection)!}
              className="inline-flex items-center justify-center rounded-lg bg-primary px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-primary/90"
            >
              {providerText(t, "onboardingOpenProviderDetails", "Open provider details")}
            </Link>
          )}

<Link
href="/dashboard/providers"
className="inline-flex items-center justify-center rounded-lg border border-border bg-bg-subtle px-4 py-2 text-sm font-medium text-text-main transition-colors hover:bg-bg-card"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { OnboardingConnection } from "./providerOnboardingApi";

export interface OnboardingSummaryAction {
href: string | null;
label: string;
}

/**
* Minimum surface area required to compute the dashboard details href.
* Accepts either a full {@link OnboardingConnection} or any partial that
* exposes the server-assigned UUID under `id`.
*/
export type HrefConnection = Pick<OnboardingConnection, "id"> & {
provider?: string;
};

/**
* Build the "open provider details" link target for the onboarding wizard
* success card. The dashboard detail route is keyed by the connection's
* server-assigned UUID (`OnboardingConnection.id`), not by the provider
* category (`connection.provider` is e.g. "openai-compatible" and is shared
* across many connections).
*
* Returns `null` if no usable id is present so callers can hide the action
* entirely rather than linking to a 404.
*/
export function buildProviderDetailsHref(
connection: HrefConnection | null | undefined
): string | null {
const id = connection?.id?.trim();
if (!id) return null;
return `/dashboard/providers/${encodeURIComponent(id)}`;
}
37 changes: 37 additions & 0 deletions tests/unit/provider-onboarding-href.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { test } from "node:test";
import assert from "node:assert/strict";
import { buildProviderDetailsHref } from "../../src/app/(dashboard)/dashboard/providers/components/onboarding/providerOnboardingHref";

test("buildProviderDetailsHref uses the server-assigned connection UUID", () => {
const href = buildProviderDetailsHref({
id: "9f3c1a4d-1c2b-4a3c-8def-0123456789ab",
});
assert.equal(
href,
"/dashboard/providers/9f3c1a4d-1c2b-4a3c-8def-0123456789ab"
);
});

test("buildProviderDetailsHref does not leak the provider category into the URL", () => {
// Regression for issue #6144: previously the wizard used
// `connection.provider` ("openai-compatible") as the URL slug, which 404'd
// on /dashboard/providers/[id] because that route is keyed by UUID.
const href = buildProviderDetailsHref({
id: "9f3c1a4d-1c2b-4a3c-8def-0123456789ab",
provider: "openai-compatible",
});
assert.notEqual(href, "/dashboard/providers/openai-compatible");
assert.match(href ?? "", /\/dashboard\/providers\/[0-9a-f-]+$/);
});

test("buildProviderDetailsHref returns null when no id is available", () => {
assert.equal(buildProviderDetailsHref(null), null);
assert.equal(buildProviderDetailsHref(undefined), null);
assert.equal(buildProviderDetailsHref({ id: "" }), null);
assert.equal(buildProviderDetailsHref({ id: " " }), null);
});

test("buildProviderDetailsHref percent-encodes unusual ids", () => {
const href = buildProviderDetailsHref({ id: "abc/123 def" });
assert.equal(href, "/dashboard/providers/abc%2F123%20def");
});
Loading