This commit is contained in:
cursor[bot] 2026-08-27 19:43:21 -05:00 • committed by GitHub
commit 223f1dbec9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 49 additions and 8 deletions

View file

@ -10,10 +10,18 @@ export interface OrganizationListFilters {
org_alias?: string | null;
}
export const useOrganizations = (filters?: OrganizationListFilters): UseQueryResult<Organization[]> => {
export interface UseOrganizationsOptions {
enabled?: boolean;
}
export const useOrganizations = (
filters?: OrganizationListFilters,
options?: UseOrganizationsOptions,
): UseQueryResult<Organization[]> => {
const { accessToken, userId, userRole } = useAuthorized();
const orgId = filters?.org_id || null;
const orgAlias = filters?.org_alias || null;
const enabled = options?.enabled ?? true;
return useQuery<Organization[]>({
queryKey: organizationKeys.list(
orgId || orgAlias
@ -21,7 +29,7 @@ export const useOrganizations = (filters?: OrganizationListFilters): UseQueryRes
: {},
),
queryFn: async () => await organizationListCall(accessToken!, orgId, orgAlias),
enabled: Boolean(accessToken && userId && userRole),
enabled: enabled && Boolean(accessToken && userId && userRole),
});
};

View file

@ -1,13 +1,13 @@
"use client";
import { hasCapability, type Capability } from "@/utils/capabilities";
import { capabilityRequiresOrgAdmin, hasCapability, type Capability } from "@/utils/capabilities";
import useAuthorized from "./useAuthorized";
import useIsOrgAdmin from "./useIsOrgAdmin";
const useCan = (capability: Capability): boolean => {
const { userRole } = useAuthorized();
const isOrgAdmin = useIsOrgAdmin();
const isOrgAdmin = useIsOrgAdmin({ enabled: capabilityRequiresOrgAdmin(capability) });
return hasCapability(userRole, capability, isOrgAdmin);
};

View file

@ -5,10 +5,15 @@ import { isOrgAdminForAnyOrg, isOrgAdminSessionRole } from "@/utils/roles";
import { useOrganizations } from "./organizations/useOrganizations";
import useAuthorized from "./useAuthorized";
const useIsOrgAdmin = (): boolean => {
export interface UseIsOrgAdminOptions {
enabled?: boolean;
}
const useIsOrgAdmin = (options?: UseIsOrgAdminOptions): boolean => {
const { userId, userRole } = useAuthorized();
const { data: organizations } = useOrganizations();
return isOrgAdminSessionRole(userRole) || isOrgAdminForAnyOrg(organizations, userId);
const enabled = options?.enabled ?? true;
const { data: organizations } = useOrganizations(undefined, { enabled });
return isOrgAdminSessionRole(userRole) || (enabled && isOrgAdminForAnyOrg(organizations, userId));
};
export default useIsOrgAdmin;

View file

@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import { hasCapability, rolesWithCapability, type Capability } from "./capabilities";
import { capabilityRequiresOrgAdmin, hasCapability, rolesWithCapability, type Capability } from "./capabilities";
import { effectiveSessionRole } from "./roles";
const ADMIN_ROLES = ["Admin", "Admin Viewer", "proxy_admin", "proxy_admin_viewer"];
@ -150,3 +150,29 @@ describe("rolesWithCapability", () => {
expect(hasCapability(removed, "viewToolPolicies")).toBe(true);
});
});
// Callers use this to skip the organization-list fetch that backs the org-admin
// check when the capability cannot be reopened by an org-admin membership. That
// keeps role-gated pages like Memory / Workflows / Guardrails Monitor from
// firing a network request just to compute a denial.
describe("capabilityRequiresOrgAdmin", () => {
it("is true only for viewDeletedTeams, the sole capability that opts org admins in", () => {
expect(capabilityRequiresOrgAdmin("viewDeletedTeams")).toBe(true);
});
it.each<Capability>([
"viewToolPolicies",
"viewAuditLogs",
"viewPolicies",
"viewPrompts",
"viewOrganizationUsage",
"viewAgentUsage",
"viewGlobalSpend",
"viewWorkflowRuns",
"viewMemory",
"viewGuardrailUsage",
"viewProxyWideCostData",
])("is false for %s so useCan can skip the org-list fetch for it", (capability) => {
expect(capabilityRequiresOrgAdmin(capability)).toBe(false);
});
});

View file

@ -24,6 +24,8 @@ const ORG_ADMIN_CAPABILITIES: ReadonlySet<Capability> = new Set<Capability>([
"viewOrganizationUsage",
]);
export const capabilityRequiresOrgAdmin = (capability: Capability): boolean => ORG_ADMIN_CAPABILITIES.has(capability);
export const hasCapability = (
userRole: string | null | undefined,
capability: Capability,