72 lines · 3.5 KB · TypeScript
RawDownload
import { useEffect, type ReactNode } from "react";import { useNavigate, useParams, useRouterState } from "@tanstack/react-router"; // ---------------------------------------------------------------------------// Org-slug URL canonicalization for org-scoped hosts (cloud, self-host,// cloudflare). Console routes live under an optional `{-$orgSlug}` segment, so// the same tree serves `/policies` and `/acme/policies`; this gate, mounted// inside the authenticated shell, pins the URL to the active org's slug:////   - bare URL                  → replace with `/<active-slug>/…` (canonicalize)//   - active slug already in URL → render//   - any other slug in URL     → replace with `/<active-slug>/…` (canonicalize)//// The URL slug is the request SCOPE, not just a label: every API call carries// it (the `x-executor-organization` header), and the server re-checks live// membership and resolves data for that org — same as the MCP URL-pinned org.// So a foreign slug never reaches this gate as "active" on a multi-org host:// the server returns no organization for an org the caller can't see, and the// shell 404s upstream. The host is what enforces that — it must not build this// subtree until the authenticated answer NAMES the slug in the URL, or an// optimistic answer about the session's own org (cloud's auth-hint cookie)// would mount the shell under a foreign address before the server ever// replies. That makes two browser tabs on different orgs fully// independent — no shared "active org" to steal. On a single-org host (e.g.// self-host) every slug resolves to the same org server-side, so a bogus slug// (e.g. `/totally-bogus`) would otherwise fuzzy-match a route and render// under the wrong URL forever; canonicalizing it here fixes the URL instead.//// A genuinely unmatched path is neither of the above: it has no `orgSlug`// param (nothing below root matched) but isn't a bare URL either, so it must// NOT canonicalize — doing so would silently rewrite a bad URL into a valid// one instead of letting the not-found page render.// --------------------------------------------------------------------------- export interface OrgSlugGateProps {  /** The active organization's slug (from `useAuth().organization`). */  readonly activeSlug: string;  readonly children: ReactNode;} export function OrgSlugGate(props: OrgSlugGateProps) {  const { activeSlug } = props;  const params = useParams({ strict: false }) as { orgSlug?: string };  const urlSlug = params.orgSlug ?? null;  const navigate = useNavigate();   // Skip canonicalization whenever the router is currently sitting on a  // not-found match — see the file header for why.  const isNotFound = useRouterState({    select: (state) => state.matches.some((match) => match.globalNotFound),  });   const needsCanonicalize = urlSlug !== activeSlug && !isNotFound;   useEffect(() => {    if (!needsCanonicalize) return;    // Re-target the CURRENT route with the active slug. `to: "."` +    // `search: true` keeps path and query (deep links like    // `/integrations/add/mcp?url=…` canonicalize in place); only the orgSlug    // param changes.    void navigate({      to: ".",      params: (previous: Record<string, string>) => ({ ...previous, orgSlug: activeSlug }),      search: true,      replace: true,    });  }, [needsCanonicalize, activeSlug, navigate]);   // Render through while canonicalizing — the target is the same route, so  // withholding children would only flash the page.  return <>{props.children}</>;}