From 710eb00800459cff7a3925827a188bc3b7408bc1 Mon Sep 17 00:00:00 2001 From: Laura Beatris Date: Wed, 20 May 2026 09:49:39 -0300 Subject: [PATCH 1/5] Render route in OrganizationProfile --- packages/localizations/src/en-US.ts | 1 + .../src/internal/clerk-js/componentGuards.ts | 1 + packages/shared/src/types/localization.ts | 1 + .../OrganizationProfileRoutes.tsx | 17 ++++++ .../OrganizationSelfServeSsoPage.tsx | 24 ++++++++ .../__tests__/OrganizationProfile.test.tsx | 61 +++++++++++++++++++ packages/ui/src/constants.ts | 1 + .../components/OrganizationProfile.ts | 8 +++ packages/ui/src/icons/connections.svg | 3 + packages/ui/src/icons/index.ts | 13 ++-- packages/ui/src/utils/createCustomPages.tsx | 24 +++++++- 11 files changed, 146 insertions(+), 8 deletions(-) create mode 100644 packages/ui/src/components/OrganizationProfile/OrganizationSelfServeSsoPage.tsx create mode 100644 packages/ui/src/icons/connections.svg diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts index a9a83381088..65f678f7254 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -768,6 +768,7 @@ export const enUS: LocalizationResource = { navbar: { apiKeys: 'API keys', billing: 'Billing', + selfServeSso: 'Self-Serve SSO', description: 'Manage your organization.', general: 'General', members: 'Members', diff --git a/packages/shared/src/internal/clerk-js/componentGuards.ts b/packages/shared/src/internal/clerk-js/componentGuards.ts index 06f9ac443b9..f0ae39f80f0 100644 --- a/packages/shared/src/internal/clerk-js/componentGuards.ts +++ b/packages/shared/src/internal/clerk-js/componentGuards.ts @@ -18,6 +18,7 @@ export const noOrganizationExists: ComponentGuard = clerk => { return !clerk.organization; }; +// TODO -> Update with per org check export const disabledOrganizationsFeature: ComponentGuard = (_, environment) => { return !environment?.organizationSettings.enabled; }; diff --git a/packages/shared/src/types/localization.ts b/packages/shared/src/types/localization.ts index db7df988428..6664a920479 100644 --- a/packages/shared/src/types/localization.ts +++ b/packages/shared/src/types/localization.ts @@ -1021,6 +1021,7 @@ export type __internal_LocalizationResource = { members: LocalizationValue; billing: LocalizationValue; apiKeys: LocalizationValue; + selfServeSso: LocalizationValue; }; badge__unverified: LocalizationValue; badge__automaticInvitation: LocalizationValue; diff --git a/packages/ui/src/components/OrganizationProfile/OrganizationProfileRoutes.tsx b/packages/ui/src/components/OrganizationProfile/OrganizationProfileRoutes.tsx index f609259d947..4976b4658ed 100644 --- a/packages/ui/src/components/OrganizationProfile/OrganizationProfileRoutes.tsx +++ b/packages/ui/src/components/OrganizationProfile/OrganizationProfileRoutes.tsx @@ -37,6 +37,12 @@ const OrganizationPaymentAttemptPage = lazy(() => })), ); +const OrganizationSelfServeSsoPage = lazy(() => + import(/* webpackChunkName: "op-self-serve-sso-page"*/ './OrganizationSelfServeSsoPage').then(module => ({ + default: module.OrganizationSelfServeSsoPage, + })), +); + export const OrganizationProfileRoutes = () => { const { pages, @@ -44,7 +50,9 @@ export const OrganizationProfileRoutes = () => { isGeneralPageRoot, isBillingPageRoot, isAPIKeysPageRoot, + isSelfServeSsoPageRoot, shouldShowBilling, + shouldShowSelfServeSso, apiKeysProps, } = useOrganizationProfileContext(); @@ -142,6 +150,15 @@ export const OrganizationProfileRoutes = () => { )} + {shouldShowSelfServeSso ? ( + + + + + + + + ) : null} ); diff --git a/packages/ui/src/components/OrganizationProfile/OrganizationSelfServeSsoPage.tsx b/packages/ui/src/components/OrganizationProfile/OrganizationSelfServeSsoPage.tsx new file mode 100644 index 00000000000..baa858bd5ab --- /dev/null +++ b/packages/ui/src/components/OrganizationProfile/OrganizationSelfServeSsoPage.tsx @@ -0,0 +1,24 @@ +import { useOrganization } from '@clerk/shared/react'; + +import { Col } from '@/ui/customizables'; +import { Header } from '@/ui/elements/Header'; + +export const OrganizationSelfServeSsoPage = () => { + const { organization } = useOrganization(); + + if (!organization) { + // We should never reach this point, but we'll return null to make TS happy + return null; + } + + return ( + + + + + + ); +}; diff --git a/packages/ui/src/components/OrganizationProfile/__tests__/OrganizationProfile.test.tsx b/packages/ui/src/components/OrganizationProfile/__tests__/OrganizationProfile.test.tsx index 3a4d3b0b46a..0baedb698c9 100644 --- a/packages/ui/src/components/OrganizationProfile/__tests__/OrganizationProfile.test.tsx +++ b/packages/ui/src/components/OrganizationProfile/__tests__/OrganizationProfile.test.tsx @@ -476,6 +476,67 @@ describe('OrganizationProfile', () => { }); }); + describe('Self-Serve SSO visibility', () => { + it('includes Self-Serve SSO when self-serve SSO is enabled in environment settings', async () => { + const { wrapper } = await createFixtures(f => { + f.withEnterpriseSso({ selfServeSso: true }); + f.withOrganizations(); + f.withUser({ + email_addresses: ['test@clerk.com'], + organization_memberships: [ + { + name: 'Org1', + permissions: ['org:sys_entconns:manage'], + }, + ], + }); + }); + + render(, { wrapper }); + expect(await screen.findByText('Self-Serve SSO')).toBeDefined(); + }); + + it('does not include Self-Serve SSO when self-serve SSO is disabled in environment settings', async () => { + const { wrapper } = await createFixtures(f => { + f.withEnterpriseSso({ selfServeSso: false }); + f.withOrganizations(); + f.withUser({ + email_addresses: ['test@clerk.com'], + organization_memberships: [ + { + name: 'Org1', + permissions: ['org:sys_entconns:manage'], + }, + ], + }); + }); + + const { queryByText } = render(, { wrapper }); + await waitFor(() => expect(queryByText('Self-Serve SSO')).toBeNull()); + }); + + // We keep showing the section but the internal page displays a warning for missing permission + it('includes Self-Serve SSO even when the user does not have the manage enterprise connections permission', async () => { + const { wrapper } = await createFixtures(f => { + f.withEnterpriseSso({ selfServeSso: true }); + f.withOrganizations(); + f.withUser({ + email_addresses: ['test@clerk.com'], + organization_memberships: [ + { + name: 'Org1', + permissions: [], + }, + ], + }); + }); + + // TODO -> Add assertions for page content warning + render(, { wrapper }); + expect(await screen.findByText('Self-Serve SSO')).toBeDefined(); + }); + }); + it('removes member nav item if user is lacking permissions', async () => { const { wrapper } = await createFixtures(f => { f.withOrganizations(); diff --git a/packages/ui/src/constants.ts b/packages/ui/src/constants.ts index 430524de6f9..a46896ee7ff 100644 --- a/packages/ui/src/constants.ts +++ b/packages/ui/src/constants.ts @@ -12,6 +12,7 @@ export const ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID = { MEMBERS: 'members', BILLING: 'billing', API_KEYS: 'apiKeys', + SELF_SERVE_SSO: 'selfServeSso', }; export const USER_BUTTON_ITEM_ID = { diff --git a/packages/ui/src/contexts/components/OrganizationProfile.ts b/packages/ui/src/contexts/components/OrganizationProfile.ts index b1210adb29d..5db4af1194a 100644 --- a/packages/ui/src/contexts/components/OrganizationProfile.ts +++ b/packages/ui/src/contexts/components/OrganizationProfile.ts @@ -25,7 +25,9 @@ export type OrganizationProfileContextType = OrganizationProfileCtx & { isGeneralPageRoot: boolean; isBillingPageRoot: boolean; isAPIKeysPageRoot: boolean; + isSelfServeSsoPageRoot: boolean; shouldShowBilling: boolean; + shouldShowSelfServeSso: boolean; }; export const OrganizationProfileContext = createContext(null); @@ -56,6 +58,9 @@ export const useOrganizationProfileContext = (): OrganizationProfileContextType // The C2 had a subscription in the past Boolean(statements.data.length > 0); + // TODO -> Check for org level as well + const shouldShowSelfServeSso = environment.userSettings.enterpriseSSO.self_serve_sso; + const pages = useMemo( () => createOrganizationProfileCustomPages(customPages || [], clerk, shouldShowBilling, environment), [customPages, shouldShowBilling], @@ -68,6 +73,7 @@ export const useOrganizationProfileContext = (): OrganizationProfileContextType const isGeneralPageRoot = pages.routes[0].id === ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID.GENERAL; const isBillingPageRoot = pages.routes[0].id === ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID.BILLING; const isAPIKeysPageRoot = pages.routes[0].id === ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID.API_KEYS; + const isSelfServeSsoPageRoot = pages.routes[0].id === ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID.SELF_SERVE_SSO; const navigateToGeneralPageRoot = () => navigate(isGeneralPageRoot ? '../' : isMembersPageRoot ? './organization-general' : '../organization-general'); @@ -81,6 +87,8 @@ export const useOrganizationProfileContext = (): OrganizationProfileContextType isGeneralPageRoot, isBillingPageRoot, isAPIKeysPageRoot, + isSelfServeSsoPageRoot, shouldShowBilling, + shouldShowSelfServeSso, }; }; diff --git a/packages/ui/src/icons/connections.svg b/packages/ui/src/icons/connections.svg new file mode 100644 index 00000000000..3281813c8d9 --- /dev/null +++ b/packages/ui/src/icons/connections.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/ui/src/icons/index.ts b/packages/ui/src/icons/index.ts index 0fd1f281bdb..c3c7a836834 100644 --- a/packages/ui/src/icons/index.ts +++ b/packages/ui/src/icons/index.ts @@ -14,20 +14,21 @@ export { default as AuthApp } from './auth-app.svg'; export { default as Billing } from './billing.svg'; export { default as Block } from './block.svg'; export { default as BoxIcon } from './box.svg'; -export { default as Caret } from './caret.svg'; export { default as CaretLeft } from './caret-left.svg'; export { default as CaretRight } from './caret-right.svg'; +export { default as Caret } from './caret.svg'; export { default as ChatAltIcon } from './chat-alt.svg'; -export { default as Check } from './check.svg'; export { default as CheckCircle } from './check-circle.svg'; +export { default as Check } from './check.svg'; export { default as CheckmarkFilled } from './checkmark-filled.svg'; export { default as ChevronDown } from './chevron-down.svg'; export { default as ChevronUpDown } from './chevron-up-down.svg'; -export { default as Clipboard } from './clipboard.svg'; export { default as ClipboardOutline } from './clipboard-outline.svg'; +export { default as Clipboard } from './clipboard.svg'; export { default as Close } from './close.svg'; export { default as Code } from './code.svg'; export { default as CogFilled } from './cog-filled.svg'; +export { default as Connections } from './connections.svg'; export { default as Copy } from './copy.svg'; export { default as CreditCard } from './credit-card.svg'; export { default as DeviceLaptop } from './device-laptop.svg'; @@ -38,8 +39,8 @@ export { default as DuotoneAtSymbol } from './duotone-at-symbol.svg'; export { default as Email } from './email.svg'; export { default as ExclamationCircle } from './exclamation-circle.svg'; export { default as ExclamationTriangle } from './exclamation-triangle.svg'; -export { default as Eye } from './eye.svg'; export { default as EyeSlash } from './eye-slash.svg'; +export { default as Eye } from './eye.svg'; export { default as Fingerprint } from './fingerprint.svg'; export { default as Folder } from './folder.svg'; export { default as GenericPayment } from './generic-pay.svg'; @@ -53,8 +54,8 @@ export { default as Menu } from './menu.svg'; export { default as Minus } from './minus.svg'; export { default as Mobile } from './mobile-small.svg'; export { default as Organization } from './organization.svg'; -export { default as Pencil } from './pencil.svg'; export { default as PencilEdit } from './pencil-edit.svg'; +export { default as Pencil } from './pencil.svg'; export { default as Plans } from './plans.svg'; export { default as Plus } from './plus.svg'; export { default as Print } from './print.svg'; @@ -62,8 +63,8 @@ export { default as QuestionMark } from './question-mark.svg'; export { default as RequestAuthIcon } from './request-auth.svg'; export { default as RotateLeftRight } from './rotate-left-right.svg'; export { default as Selector } from './selector.svg'; -export { default as SignOut } from './signout.svg'; export { default as SignOutDouble } from './signout-double.svg'; +export { default as SignOut } from './signout.svg'; export { default as SpinnerJumbo } from './spinner-jumbo.svg'; export { default as SwitchArrowRight } from './switch-arrow-right.svg'; export { default as SwitchArrows } from './switch-arrows.svg'; diff --git a/packages/ui/src/utils/createCustomPages.tsx b/packages/ui/src/utils/createCustomPages.tsx index d3a03b782df..0e50462a1da 100644 --- a/packages/ui/src/utils/createCustomPages.tsx +++ b/packages/ui/src/utils/createCustomPages.tsx @@ -1,6 +1,7 @@ import { disabledOrganizationAPIKeysFeature, disabledOrganizationBillingFeature, + disabledSelfServeSSOFeature, disabledUserAPIKeysFeature, disabledUserBillingFeature, } from '@clerk/shared/internal/clerk-js/componentGuards'; @@ -9,7 +10,7 @@ import type { CustomPage, EnvironmentResource, LoadedClerk } from '@clerk/shared import { ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID, USER_PROFILE_NAVBAR_ROUTE_ID } from '../constants'; import type { NavbarRoute } from '../elements/Navbar'; -import { Code, CreditCard, Organization, TickShield, User, Users } from '../icons'; +import { Code, Connections, CreditCard, Organization, TickShield, User, Users } from '../icons'; import { localizationKeys } from '../localization'; import { ExternalElementMounter } from './ExternalElementMounter'; import { isDevelopmentSDK } from './runtimeEnvironment'; @@ -48,7 +49,15 @@ type GetDefaultRoutesReturnType = { type CreateCustomPagesParams = { customPages: CustomPage[]; - getDefaultRoutes: ({ commerce, apiKeys }: { commerce: boolean; apiKeys: boolean }) => GetDefaultRoutesReturnType; + getDefaultRoutes: ({ + commerce, + apiKeys, + selfServeSso, + }: { + commerce: boolean; + apiKeys: boolean; + selfServeSso: boolean; + }) => GetDefaultRoutesReturnType; setFirstPathToRoot: (routes: NavbarRoute[]) => NavbarRoute[]; excludedPathsFromDuplicateWarning: string[]; }; @@ -106,6 +115,7 @@ const createCustomPages = ( apiKeys: organization ? !disabledOrganizationAPIKeysFeature(clerk, environment) : !disabledUserAPIKeysFeature(clerk, environment), + selfServeSso: organization ? !disabledSelfServeSSOFeature(clerk, environment) : false, }); if (isDevelopmentSDK(clerk)) { @@ -315,9 +325,11 @@ const getUserProfileDefaultRoutes = ({ const getOrganizationProfileDefaultRoutes = ({ commerce, apiKeys, + selfServeSso, }: { commerce: boolean; apiKeys: boolean; + selfServeSso: boolean; }): GetDefaultRoutesReturnType => { const INITIAL_ROUTES: NavbarRoute[] = [ { @@ -349,6 +361,14 @@ const getOrganizationProfileDefaultRoutes = ({ path: 'organization-api-keys', }); } + if (selfServeSso) { + INITIAL_ROUTES.push({ + name: localizationKeys('organizationProfile.navbar.selfServeSso'), + id: ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID.SELF_SERVE_SSO, + icon: Connections, + path: 'organization-self-serve-sso', + }); + } const pageToRootNavbarRouteMap: Record = { 'invite-members': INITIAL_ROUTES.find(r => r.id === ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID.MEMBERS) as NavbarRoute, From 86ce67203c1b43abd2b233b73904dea19e0caf9d Mon Sep 17 00:00:00 2001 From: Laura Beatris Date: Wed, 20 May 2026 10:08:10 -0300 Subject: [PATCH 2/5] Render `ConfigureSSO` content within `OrganizationProfile` --- .../src/components/ConfigureSSO/ConfigureSSO.tsx | 10 +++++----- .../OrganizationSelfServeSsoPage.tsx | 16 ++++++---------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx b/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx index 399672d1cdb..1e8ef611049 100644 --- a/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx +++ b/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx @@ -58,16 +58,16 @@ const AuthenticatedContent = withCoreUserGuard(() => { flex: 1, })} > - - - + + + ); }); -const ConfigureSSOCardContent = ({ contentRef }: { contentRef: React.RefObject }) => { +export const ConfigureSSOContent = ({ contentRef }: { contentRef: React.RefObject }) => { const { data: enterpriseConnections, isLoading: isLoadingEnterpriseConnections, @@ -142,7 +142,7 @@ const ConfigureSSOSteps = () => { ); }; -const ConfigureSSOCardProtect = ({ children }: { children: React.ReactNode }) => { +export const ConfigureSSOProtect = ({ children }: { children: React.ReactNode }) => { const { session } = useSession(); const isPersonalWorkspace = !session?.lastActiveOrganizationId; const canManageEnterpriseConnections = useProtect( diff --git a/packages/ui/src/components/OrganizationProfile/OrganizationSelfServeSsoPage.tsx b/packages/ui/src/components/OrganizationProfile/OrganizationSelfServeSsoPage.tsx index baa858bd5ab..94b5e943a73 100644 --- a/packages/ui/src/components/OrganizationProfile/OrganizationSelfServeSsoPage.tsx +++ b/packages/ui/src/components/OrganizationProfile/OrganizationSelfServeSsoPage.tsx @@ -1,10 +1,11 @@ import { useOrganization } from '@clerk/shared/react'; +import { useRef } from 'react'; -import { Col } from '@/ui/customizables'; -import { Header } from '@/ui/elements/Header'; +import { ConfigureSSOContent, ConfigureSSOProtect } from '../ConfigureSSO/ConfigureSSO'; export const OrganizationSelfServeSsoPage = () => { const { organization } = useOrganization(); + const contentRef = useRef(null); if (!organization) { // We should never reach this point, but we'll return null to make TS happy @@ -12,13 +13,8 @@ export const OrganizationSelfServeSsoPage = () => { } return ( - - - - - + + + ); }; From d8fe3f8cd4ded3edddb2208be028a322329cc0d7 Mon Sep 17 00:00:00 2001 From: Laura Beatris Date: Thu, 21 May 2026 07:27:00 -0300 Subject: [PATCH 3/5] Update to new permission key --- packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx b/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx index 1e8ef611049..4366801512d 100644 --- a/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx +++ b/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx @@ -146,7 +146,7 @@ export const ConfigureSSOProtect = ({ children }: { children: React.ReactNode }) const { session } = useSession(); const isPersonalWorkspace = !session?.lastActiveOrganizationId; const canManageEnterpriseConnections = useProtect( - has => isPersonalWorkspace || has({ permission: 'org:sys_enterprise_connections:manage' }), + has => isPersonalWorkspace || has({ permission: 'org:sys_entconns:manage' }), ); if (!canManageEnterpriseConnections) { From 07abb56c89a997c1a2b17091f3c1ec5ae86b015b Mon Sep 17 00:00:00 2001 From: Laura Beatris Date: Thu, 21 May 2026 10:24:05 -0300 Subject: [PATCH 4/5] Add `self_serve_sso_enabled` to `Organization` --- .../src/core/resources/Organization.ts | 3 ++ .../resources/__tests__/Organization.test.ts | 22 +++++++++++ packages/localizations/src/en-US.ts | 2 +- .../src/internal/clerk-js/componentGuards.ts | 1 - packages/shared/src/types/json.ts | 1 + packages/shared/src/types/localization.ts | 2 +- packages/shared/src/types/organization.ts | 5 +++ .../__tests__/ConfigureSSO.test.tsx | 6 +-- .../OrganizationProfileRoutes.tsx | 12 +++--- ...e.tsx => OrganizationSelfServeSSOPage.tsx} | 2 +- .../__tests__/OrganizationProfile.test.tsx | 38 +++++++++++++++---- packages/ui/src/constants.ts | 2 +- .../components/OrganizationProfile.ts | 22 +++++++---- packages/ui/src/test/fixture-helpers.ts | 4 +- packages/ui/src/utils/createCustomPages.tsx | 17 +++++---- 15 files changed, 101 insertions(+), 38 deletions(-) rename packages/ui/src/components/OrganizationProfile/{OrganizationSelfServeSsoPage.tsx => OrganizationSelfServeSSOPage.tsx} (91%) diff --git a/packages/clerk-js/src/core/resources/Organization.ts b/packages/clerk-js/src/core/resources/Organization.ts index 00f6e2632ca..f0a2a21b9f2 100644 --- a/packages/clerk-js/src/core/resources/Organization.ts +++ b/packages/clerk-js/src/core/resources/Organization.ts @@ -49,6 +49,7 @@ export class Organization extends BaseResource implements OrganizationResource { membersCount = 0; pendingInvitationsCount = 0; maxAllowedMemberships!: number; + selfServeSSOEnabled = false; constructor(data: OrganizationJSON | OrganizationJSONSnapshot) { super(); @@ -303,6 +304,7 @@ export class Organization extends BaseResource implements OrganizationResource { this.pendingInvitationsCount = data.pending_invitations_count || 0; this.maxAllowedMemberships = data.max_allowed_memberships || 0; this.adminDeleteEnabled = data.admin_delete_enabled || false; + this.selfServeSSOEnabled = data.self_serve_sso_enabled || false; this.createdAt = unixEpochToDate(data.created_at); this.updatedAt = unixEpochToDate(data.updated_at); return this; @@ -321,6 +323,7 @@ export class Organization extends BaseResource implements OrganizationResource { pending_invitations_count: this.pendingInvitationsCount, max_allowed_memberships: this.maxAllowedMemberships, admin_delete_enabled: this.adminDeleteEnabled, + self_serve_sso_enabled: this.selfServeSSOEnabled, created_at: this.createdAt.getTime(), updated_at: this.updatedAt.getTime(), }; diff --git a/packages/clerk-js/src/core/resources/__tests__/Organization.test.ts b/packages/clerk-js/src/core/resources/__tests__/Organization.test.ts index 0617640806d..b6ba4c12892 100644 --- a/packages/clerk-js/src/core/resources/__tests__/Organization.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/Organization.test.ts @@ -19,6 +19,7 @@ describe('Organization', () => { admin_delete_enabled: true, max_allowed_memberships: 3, has_image: true, + self_serve_sso_enabled: true, }); expect(organization).toMatchObject({ @@ -32,6 +33,7 @@ describe('Organization', () => { pendingInvitationsCount: 10, maxAllowedMemberships: 3, adminDeleteEnabled: true, + selfServeSSOEnabled: true, createdAt: expect.any(Date), updatedAt: expect.any(Date), publicMetadata: { @@ -39,4 +41,24 @@ describe('Organization', () => { }, }); }); + + it('defaults selfServeSSOEnabled to false when the field is omitted from FAPI', () => { + const organization = new Organization({ + object: 'organization', + id: 'test_id', + name: 'test_name', + public_metadata: {}, + slug: 'test_slug', + image_url: '', + created_at: 12345, + updated_at: 5678, + members_count: 1, + pending_invitations_count: 0, + admin_delete_enabled: true, + max_allowed_memberships: 3, + has_image: false, + }); + + expect(organization.selfServeSSOEnabled).toBe(false); + }); }); diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts index 65f678f7254..25bf4ad540a 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -768,7 +768,7 @@ export const enUS: LocalizationResource = { navbar: { apiKeys: 'API keys', billing: 'Billing', - selfServeSso: 'Self-Serve SSO', + selfServeSSO: 'Single Sign-On (SSO)', description: 'Manage your organization.', general: 'General', members: 'Members', diff --git a/packages/shared/src/internal/clerk-js/componentGuards.ts b/packages/shared/src/internal/clerk-js/componentGuards.ts index f0ae39f80f0..06f9ac443b9 100644 --- a/packages/shared/src/internal/clerk-js/componentGuards.ts +++ b/packages/shared/src/internal/clerk-js/componentGuards.ts @@ -18,7 +18,6 @@ export const noOrganizationExists: ComponentGuard = clerk => { return !clerk.organization; }; -// TODO -> Update with per org check export const disabledOrganizationsFeature: ComponentGuard = (_, environment) => { return !environment?.organizationSettings.enabled; }; diff --git a/packages/shared/src/types/json.ts b/packages/shared/src/types/json.ts index 7c91ed39498..4b39d9cf8a3 100644 --- a/packages/shared/src/types/json.ts +++ b/packages/shared/src/types/json.ts @@ -393,6 +393,7 @@ export interface OrganizationJSON extends ClerkResourceJSON { pending_invitations_count: number; admin_delete_enabled: boolean; max_allowed_memberships: number; + self_serve_sso_enabled?: boolean; } export interface OrganizationMembershipJSON extends ClerkResourceJSON { diff --git a/packages/shared/src/types/localization.ts b/packages/shared/src/types/localization.ts index 6664a920479..e41306f099c 100644 --- a/packages/shared/src/types/localization.ts +++ b/packages/shared/src/types/localization.ts @@ -1021,7 +1021,7 @@ export type __internal_LocalizationResource = { members: LocalizationValue; billing: LocalizationValue; apiKeys: LocalizationValue; - selfServeSso: LocalizationValue; + selfServeSSO: LocalizationValue; }; badge__unverified: LocalizationValue; badge__automaticInvitation: LocalizationValue; diff --git a/packages/shared/src/types/organization.ts b/packages/shared/src/types/organization.ts index 98ced75e217..0843c9859cd 100644 --- a/packages/shared/src/types/organization.ts +++ b/packages/shared/src/types/organization.ts @@ -46,6 +46,11 @@ export interface OrganizationResource extends ClerkResource, BillingPayerMethods publicMetadata: OrganizationPublicMetadata; adminDeleteEnabled: boolean; maxAllowedMemberships: number; + /** + * Whether the organization opted-in to self-serve SSO. Defaults to `false` + * when the instance does not have self-serve SSO enabled. + */ + selfServeSSOEnabled: boolean; createdAt: Date; updatedAt: Date; update: (params: UpdateOrganizationParams) => Promise; diff --git a/packages/ui/src/components/ConfigureSSO/__tests__/ConfigureSSO.test.tsx b/packages/ui/src/components/ConfigureSSO/__tests__/ConfigureSSO.test.tsx index b1c3e2089a6..837697684f0 100644 --- a/packages/ui/src/components/ConfigureSSO/__tests__/ConfigureSSO.test.tsx +++ b/packages/ui/src/components/ConfigureSSO/__tests__/ConfigureSSO.test.tsx @@ -11,7 +11,7 @@ describe('ConfigureSSO', () => { describe('within an organization', () => { it('shows a warning if the active organization membership lacks the manage enterprise connections permission', async () => { const { wrapper, fixtures } = await createFixtures(f => { - f.withEnterpriseSso({ selfServeSso: true }); + f.withEnterpriseSso({ selfServeSSO: true }); f.withEmailAddress(); f.withOrganizations(); f.withUser({ @@ -31,7 +31,7 @@ describe('ConfigureSSO', () => { it('renders the wizard when the active organization membership has the manage enterprise connections permission', async () => { const { wrapper, fixtures } = await createFixtures(f => { - f.withEnterpriseSso({ selfServeSso: true }); + f.withEnterpriseSso({ selfServeSSO: true }); f.withEmailAddress(); f.withOrganizations(); f.withUser({ @@ -54,7 +54,7 @@ describe('ConfigureSSO', () => { describe('in a personal workspace', () => { it('renders the wizard without checking the manage enterprise connections permission', async () => { const { wrapper, fixtures } = await createFixtures(f => { - f.withEnterpriseSso({ selfServeSso: true }); + f.withEnterpriseSso({ selfServeSSO: true }); f.withEmailAddress(); f.withUser({ email_addresses: ['test@clerk.com'] }); }); diff --git a/packages/ui/src/components/OrganizationProfile/OrganizationProfileRoutes.tsx b/packages/ui/src/components/OrganizationProfile/OrganizationProfileRoutes.tsx index 4976b4658ed..a6d1e1fd1a7 100644 --- a/packages/ui/src/components/OrganizationProfile/OrganizationProfileRoutes.tsx +++ b/packages/ui/src/components/OrganizationProfile/OrganizationProfileRoutes.tsx @@ -37,9 +37,9 @@ const OrganizationPaymentAttemptPage = lazy(() => })), ); -const OrganizationSelfServeSsoPage = lazy(() => - import(/* webpackChunkName: "op-self-serve-sso-page"*/ './OrganizationSelfServeSsoPage').then(module => ({ - default: module.OrganizationSelfServeSsoPage, +const OrganizationSelfServeSSOPage = lazy(() => + import(/* webpackChunkName: "op-self-serve-sso-page"*/ './OrganizationSelfServeSSOPage').then(module => ({ + default: module.OrganizationSelfServeSSOPage, })), ); @@ -52,7 +52,7 @@ export const OrganizationProfileRoutes = () => { isAPIKeysPageRoot, isSelfServeSsoPageRoot, shouldShowBilling, - shouldShowSelfServeSso, + shouldShowSelfServeSSO, apiKeysProps, } = useOrganizationProfileContext(); @@ -150,11 +150,11 @@ export const OrganizationProfileRoutes = () => { )} - {shouldShowSelfServeSso ? ( + {shouldShowSelfServeSSO ? ( - + diff --git a/packages/ui/src/components/OrganizationProfile/OrganizationSelfServeSsoPage.tsx b/packages/ui/src/components/OrganizationProfile/OrganizationSelfServeSSOPage.tsx similarity index 91% rename from packages/ui/src/components/OrganizationProfile/OrganizationSelfServeSsoPage.tsx rename to packages/ui/src/components/OrganizationProfile/OrganizationSelfServeSSOPage.tsx index 94b5e943a73..f3d6012eb7d 100644 --- a/packages/ui/src/components/OrganizationProfile/OrganizationSelfServeSsoPage.tsx +++ b/packages/ui/src/components/OrganizationProfile/OrganizationSelfServeSSOPage.tsx @@ -3,7 +3,7 @@ import { useRef } from 'react'; import { ConfigureSSOContent, ConfigureSSOProtect } from '../ConfigureSSO/ConfigureSSO'; -export const OrganizationSelfServeSsoPage = () => { +export const OrganizationSelfServeSSOPage = () => { const { organization } = useOrganization(); const contentRef = useRef(null); diff --git a/packages/ui/src/components/OrganizationProfile/__tests__/OrganizationProfile.test.tsx b/packages/ui/src/components/OrganizationProfile/__tests__/OrganizationProfile.test.tsx index 0baedb698c9..ed9039fc8c5 100644 --- a/packages/ui/src/components/OrganizationProfile/__tests__/OrganizationProfile.test.tsx +++ b/packages/ui/src/components/OrganizationProfile/__tests__/OrganizationProfile.test.tsx @@ -476,16 +476,17 @@ describe('OrganizationProfile', () => { }); }); - describe('Self-Serve SSO visibility', () => { - it('includes Self-Serve SSO when self-serve SSO is enabled in environment settings', async () => { + describe('SSO visibility', () => { + it('includes SSO when enabled at the instance and the org has opted in', async () => { const { wrapper } = await createFixtures(f => { - f.withEnterpriseSso({ selfServeSso: true }); + f.withEnterpriseSso({ selfServeSSO: true }); f.withOrganizations(); f.withUser({ email_addresses: ['test@clerk.com'], organization_memberships: [ { name: 'Org1', + self_serve_sso_enabled: true, permissions: ['org:sys_entconns:manage'], }, ], @@ -496,15 +497,16 @@ describe('OrganizationProfile', () => { expect(await screen.findByText('Self-Serve SSO')).toBeDefined(); }); - it('does not include Self-Serve SSO when self-serve SSO is disabled in environment settings', async () => { + it('does not include SSO when disabled at the instance level', async () => { const { wrapper } = await createFixtures(f => { - f.withEnterpriseSso({ selfServeSso: false }); + f.withEnterpriseSso({ selfServeSSO: false }); f.withOrganizations(); f.withUser({ email_addresses: ['test@clerk.com'], organization_memberships: [ { name: 'Org1', + self_serve_sso_enabled: true, permissions: ['org:sys_entconns:manage'], }, ], @@ -515,16 +517,36 @@ describe('OrganizationProfile', () => { await waitFor(() => expect(queryByText('Self-Serve SSO')).toBeNull()); }); - // We keep showing the section but the internal page displays a warning for missing permission - it('includes Self-Serve SSO even when the user does not have the manage enterprise connections permission', async () => { + it('does not include SSO when the org has not opted in, even if the instance has it enabled', async () => { const { wrapper } = await createFixtures(f => { - f.withEnterpriseSso({ selfServeSso: true }); + f.withEnterpriseSso({ selfServeSSO: true }); f.withOrganizations(); f.withUser({ email_addresses: ['test@clerk.com'], organization_memberships: [ { name: 'Org1', + self_serve_sso_enabled: false, + permissions: ['org:sys_entconns:manage'], + }, + ], + }); + }); + + const { queryByText } = render(, { wrapper }); + await waitFor(() => expect(queryByText('Self-Serve SSO')).toBeNull()); + }); + + it('includes SSO even when the user does not have the manage enterprise connections permission', async () => { + const { wrapper } = await createFixtures(f => { + f.withEnterpriseSso({ selfServeSSO: true }); + f.withOrganizations(); + f.withUser({ + email_addresses: ['test@clerk.com'], + organization_memberships: [ + { + name: 'Org1', + self_serve_sso_enabled: true, permissions: [], }, ], diff --git a/packages/ui/src/constants.ts b/packages/ui/src/constants.ts index a46896ee7ff..410f39bd0c4 100644 --- a/packages/ui/src/constants.ts +++ b/packages/ui/src/constants.ts @@ -12,7 +12,7 @@ export const ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID = { MEMBERS: 'members', BILLING: 'billing', API_KEYS: 'apiKeys', - SELF_SERVE_SSO: 'selfServeSso', + SELF_SERVE_SSO: 'selfServeSSO', }; export const USER_BUTTON_ITEM_ID = { diff --git a/packages/ui/src/contexts/components/OrganizationProfile.ts b/packages/ui/src/contexts/components/OrganizationProfile.ts index 5db4af1194a..57eb50c8b36 100644 --- a/packages/ui/src/contexts/components/OrganizationProfile.ts +++ b/packages/ui/src/contexts/components/OrganizationProfile.ts @@ -1,4 +1,4 @@ -import { useClerk } from '@clerk/shared/react'; +import { __internal_useOrganizationBase, useClerk } from '@clerk/shared/react'; import { createContext, useContext, useMemo } from 'react'; import type { NavbarRoute } from '@/ui/elements/Navbar'; @@ -27,7 +27,7 @@ export type OrganizationProfileContextType = OrganizationProfileCtx & { isAPIKeysPageRoot: boolean; isSelfServeSsoPageRoot: boolean; shouldShowBilling: boolean; - shouldShowSelfServeSso: boolean; + shouldShowSelfServeSSO: boolean; }; export const OrganizationProfileContext = createContext(null); @@ -37,6 +37,7 @@ export const useOrganizationProfileContext = (): OrganizationProfileContextType const { navigate } = useRouter(); const environment = useEnvironment(); const clerk = useClerk(); + const organization = __internal_useOrganizationBase(); if (!context || context.componentName !== 'OrganizationProfile') { throw new Error('Clerk: useOrganizationProfileContext called outside OrganizationProfile.'); @@ -58,12 +59,19 @@ export const useOrganizationProfileContext = (): OrganizationProfileContextType // The C2 had a subscription in the past Boolean(statements.data.length > 0); - // TODO -> Check for org level as well - const shouldShowSelfServeSso = environment.userSettings.enterpriseSSO.self_serve_sso; + const shouldShowSelfServeSSO = + environment.userSettings.enterpriseSSO.self_serve_sso && !!organization?.selfServeSSOEnabled; const pages = useMemo( - () => createOrganizationProfileCustomPages(customPages || [], clerk, shouldShowBilling, environment), - [customPages, shouldShowBilling], + () => + createOrganizationProfileCustomPages( + customPages || [], + clerk, + shouldShowBilling, + environment, + shouldShowSelfServeSSO, + ), + [customPages, shouldShowBilling, shouldShowSelfServeSSO], ); const navigateAfterLeaveOrganization = () => @@ -89,6 +97,6 @@ export const useOrganizationProfileContext = (): OrganizationProfileContextType isAPIKeysPageRoot, isSelfServeSsoPageRoot, shouldShowBilling, - shouldShowSelfServeSso, + shouldShowSelfServeSSO, }; }; diff --git a/packages/ui/src/test/fixture-helpers.ts b/packages/ui/src/test/fixture-helpers.ts index 79f22936c2c..4d016be6f31 100644 --- a/packages/ui/src/test/fixture-helpers.ts +++ b/packages/ui/src/test/fixture-helpers.ts @@ -538,9 +538,9 @@ const createUserSettingsFixtureHelpers = (environment: EnvironmentJSON) => { }; }; - const withEnterpriseSso = (opts?: { selfServeSso?: boolean }) => { + const withEnterpriseSso = (opts?: { selfServeSSO?: boolean }) => { us.saml = { enabled: true }; - us.enterprise_sso = { enabled: true, self_serve_sso: opts?.selfServeSso ?? false }; + us.enterprise_sso = { enabled: true, self_serve_sso: opts?.selfServeSSO ?? false }; }; const withBackupCode = (opts?: Partial) => { diff --git a/packages/ui/src/utils/createCustomPages.tsx b/packages/ui/src/utils/createCustomPages.tsx index 0e50462a1da..f6e6d2d3490 100644 --- a/packages/ui/src/utils/createCustomPages.tsx +++ b/packages/ui/src/utils/createCustomPages.tsx @@ -52,11 +52,11 @@ type CreateCustomPagesParams = { getDefaultRoutes: ({ commerce, apiKeys, - selfServeSso, + selfServeSSO, }: { commerce: boolean; apiKeys: boolean; - selfServeSso: boolean; + selfServeSSO: boolean; }) => GetDefaultRoutesReturnType; setFirstPathToRoot: (routes: NavbarRoute[]) => NavbarRoute[]; excludedPathsFromDuplicateWarning: string[]; @@ -86,6 +86,7 @@ export const createOrganizationProfileCustomPages = ( clerk: LoadedClerk, shouldShowBilling: boolean, environment?: EnvironmentResource, + shouldShowSelfServeSSO = false, ) => { return createCustomPages( { @@ -98,6 +99,7 @@ export const createOrganizationProfileCustomPages = ( shouldShowBilling, environment, true, + shouldShowSelfServeSSO, ); }; @@ -107,6 +109,7 @@ const createCustomPages = ( shouldShowBilling: boolean, environment?: EnvironmentResource, organization?: boolean, + shouldShowSelfServeSSO = false, ) => { const { INITIAL_ROUTES, pageToRootNavbarRouteMap, validReorderItemLabels } = getDefaultRoutes({ commerce: organization @@ -115,7 +118,7 @@ const createCustomPages = ( apiKeys: organization ? !disabledOrganizationAPIKeysFeature(clerk, environment) : !disabledUserAPIKeysFeature(clerk, environment), - selfServeSso: organization ? !disabledSelfServeSSOFeature(clerk, environment) : false, + selfServeSSO: organization ? shouldShowSelfServeSSO && !disabledSelfServeSSOFeature(clerk, environment) : false, }); if (isDevelopmentSDK(clerk)) { @@ -325,11 +328,11 @@ const getUserProfileDefaultRoutes = ({ const getOrganizationProfileDefaultRoutes = ({ commerce, apiKeys, - selfServeSso, + selfServeSSO, }: { commerce: boolean; apiKeys: boolean; - selfServeSso: boolean; + selfServeSSO: boolean; }): GetDefaultRoutesReturnType => { const INITIAL_ROUTES: NavbarRoute[] = [ { @@ -361,9 +364,9 @@ const getOrganizationProfileDefaultRoutes = ({ path: 'organization-api-keys', }); } - if (selfServeSso) { + if (selfServeSSO) { INITIAL_ROUTES.push({ - name: localizationKeys('organizationProfile.navbar.selfServeSso'), + name: localizationKeys('organizationProfile.navbar.selfServeSSO'), id: ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID.SELF_SERVE_SSO, icon: Connections, path: 'organization-self-serve-sso', From d951d190a2d82273321e3974042df56b24e508d8 Mon Sep 17 00:00:00 2001 From: Laura Beatris Date: Thu, 21 May 2026 11:16:21 -0300 Subject: [PATCH 5/5] Add changeset --- .changeset/dull-plums-sleep.md | 8 +++++++ .../resources/__tests__/Organization.test.ts | 20 ------------------ packages/shared/src/types/organization.ts | 4 ---- .../__tests__/ConfigureSSO.test.tsx | 2 +- .../OrganizationProfileRoutes.tsx | 4 +++- .../__tests__/OrganizationProfile.test.tsx | 21 ++++++++++++------- 6 files changed, 26 insertions(+), 33 deletions(-) create mode 100644 .changeset/dull-plums-sleep.md diff --git a/.changeset/dull-plums-sleep.md b/.changeset/dull-plums-sleep.md new file mode 100644 index 00000000000..f780c9b5ba6 --- /dev/null +++ b/.changeset/dull-plums-sleep.md @@ -0,0 +1,8 @@ +--- +'@clerk/localizations': minor +'@clerk/clerk-js': minor +'@clerk/shared': minor +'@clerk/ui': minor +--- + +Display "Single Sign-on (SSO)" section in `OrganizationProfile` if self-serve SSO is enabled on the current active organization diff --git a/packages/clerk-js/src/core/resources/__tests__/Organization.test.ts b/packages/clerk-js/src/core/resources/__tests__/Organization.test.ts index b6ba4c12892..bcdf397c8e9 100644 --- a/packages/clerk-js/src/core/resources/__tests__/Organization.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/Organization.test.ts @@ -41,24 +41,4 @@ describe('Organization', () => { }, }); }); - - it('defaults selfServeSSOEnabled to false when the field is omitted from FAPI', () => { - const organization = new Organization({ - object: 'organization', - id: 'test_id', - name: 'test_name', - public_metadata: {}, - slug: 'test_slug', - image_url: '', - created_at: 12345, - updated_at: 5678, - members_count: 1, - pending_invitations_count: 0, - admin_delete_enabled: true, - max_allowed_memberships: 3, - has_image: false, - }); - - expect(organization.selfServeSSOEnabled).toBe(false); - }); }); diff --git a/packages/shared/src/types/organization.ts b/packages/shared/src/types/organization.ts index 0843c9859cd..1bd4f266317 100644 --- a/packages/shared/src/types/organization.ts +++ b/packages/shared/src/types/organization.ts @@ -46,10 +46,6 @@ export interface OrganizationResource extends ClerkResource, BillingPayerMethods publicMetadata: OrganizationPublicMetadata; adminDeleteEnabled: boolean; maxAllowedMemberships: number; - /** - * Whether the organization opted-in to self-serve SSO. Defaults to `false` - * when the instance does not have self-serve SSO enabled. - */ selfServeSSOEnabled: boolean; createdAt: Date; updatedAt: Date; diff --git a/packages/ui/src/components/ConfigureSSO/__tests__/ConfigureSSO.test.tsx b/packages/ui/src/components/ConfigureSSO/__tests__/ConfigureSSO.test.tsx index 837697684f0..e4d9d1e501e 100644 --- a/packages/ui/src/components/ConfigureSSO/__tests__/ConfigureSSO.test.tsx +++ b/packages/ui/src/components/ConfigureSSO/__tests__/ConfigureSSO.test.tsx @@ -36,7 +36,7 @@ describe('ConfigureSSO', () => { f.withOrganizations(); f.withUser({ email_addresses: ['test@clerk.com'], - organization_memberships: [{ name: 'Org1', permissions: ['org:sys_enterprise_connections:manage'] }], + organization_memberships: [{ name: 'Org1', permissions: ['org:sys_entconns:manage'] }], }); }); diff --git a/packages/ui/src/components/OrganizationProfile/OrganizationProfileRoutes.tsx b/packages/ui/src/components/OrganizationProfile/OrganizationProfileRoutes.tsx index a6d1e1fd1a7..314f410c077 100644 --- a/packages/ui/src/components/OrganizationProfile/OrganizationProfileRoutes.tsx +++ b/packages/ui/src/components/OrganizationProfile/OrganizationProfileRoutes.tsx @@ -154,7 +154,9 @@ export const OrganizationProfileRoutes = () => { - + + + diff --git a/packages/ui/src/components/OrganizationProfile/__tests__/OrganizationProfile.test.tsx b/packages/ui/src/components/OrganizationProfile/__tests__/OrganizationProfile.test.tsx index ed9039fc8c5..859b49d9907 100644 --- a/packages/ui/src/components/OrganizationProfile/__tests__/OrganizationProfile.test.tsx +++ b/packages/ui/src/components/OrganizationProfile/__tests__/OrganizationProfile.test.tsx @@ -2,9 +2,10 @@ import type { CustomPage } from '@clerk/shared/types'; import { describe, expect, it } from 'vitest'; import { bindCreateFixtures } from '@/test/create-fixtures'; -import { render, screen, waitFor } from '@/test/utils'; +import { cleanup, render, screen, waitFor } from '@/test/utils'; import { OrganizationProfile } from '../'; +import { OrganizationSelfServeSSOPage } from '../OrganizationSelfServeSSOPage'; const { createFixtures } = bindCreateFixtures('OrganizationProfile'); @@ -494,7 +495,7 @@ describe('OrganizationProfile', () => { }); render(, { wrapper }); - expect(await screen.findByText('Self-Serve SSO')).toBeDefined(); + expect(await screen.findByText('Single Sign-On (SSO)')).toBeDefined(); }); it('does not include SSO when disabled at the instance level', async () => { @@ -514,7 +515,7 @@ describe('OrganizationProfile', () => { }); const { queryByText } = render(, { wrapper }); - await waitFor(() => expect(queryByText('Self-Serve SSO')).toBeNull()); + await waitFor(() => expect(queryByText('Single Sign-On (SSO)')).toBeNull()); }); it('does not include SSO when the org has not opted in, even if the instance has it enabled', async () => { @@ -534,10 +535,10 @@ describe('OrganizationProfile', () => { }); const { queryByText } = render(, { wrapper }); - await waitFor(() => expect(queryByText('Self-Serve SSO')).toBeNull()); + await waitFor(() => expect(queryByText('Single Sign-On (SSO)')).toBeNull()); }); - it('includes SSO even when the user does not have the manage enterprise connections permission', async () => { + it('includes SSO even when the user does not have the manage enterprise connections permission, but the page surfaces a warning', async () => { const { wrapper } = await createFixtures(f => { f.withEnterpriseSso({ selfServeSSO: true }); f.withOrganizations(); @@ -553,9 +554,15 @@ describe('OrganizationProfile', () => { }); }); - // TODO -> Add assertions for page content warning render(, { wrapper }); - expect(await screen.findByText('Self-Serve SSO')).toBeDefined(); + expect(await screen.findByText('Single Sign-On (SSO)')).toBeDefined(); + + cleanup(); + render(, { wrapper }); + expect(await screen.findByText(/you do not have permission to manage enterprise connections/i)).toBeDefined(); + expect( + screen.queryByText(/contact your organization administrator in order to have permissions/i), + ).toBeInTheDocument(); }); });