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/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..bcdf397c8e9 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: { diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts index 974c731dc4e..b0b2d822312 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -794,6 +794,7 @@ export const enUS: LocalizationResource = { navbar: { apiKeys: 'API keys', billing: 'Billing', + selfServeSSO: 'Single Sign-On (SSO)', description: 'Manage your organization.', general: 'General', members: 'Members', 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 2d0dcc7bdea..e6c2a17cf4c 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/shared/src/types/organization.ts b/packages/shared/src/types/organization.ts index 98ced75e217..1bd4f266317 100644 --- a/packages/shared/src/types/organization.ts +++ b/packages/shared/src/types/organization.ts @@ -46,6 +46,7 @@ export interface OrganizationResource extends ClerkResource, BillingPayerMethods publicMetadata: OrganizationPublicMetadata; adminDeleteEnabled: boolean; maxAllowedMemberships: number; + selfServeSSOEnabled: boolean; createdAt: Date; updatedAt: Date; update: (params: UpdateOrganizationParams) => Promise; diff --git a/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx b/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx index ecb4a1a0d4c..678f1212793 100644 --- a/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx +++ b/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx @@ -58,16 +58,14 @@ const AuthenticatedContent = withCoreUserGuard(() => { flex: 1, })} > - - - + ); }); -const ConfigureSSOCardContent = ({ contentRef }: { contentRef: React.RefObject }) => { +export const ConfigureSSOContent = ({ contentRef }: { contentRef: React.RefObject }) => { const { data: enterpriseConnections, isLoading: isLoadingEnterpriseConnections, @@ -86,16 +84,18 @@ const ConfigureSSOCardContent = ({ contentRef }: { contentRef: React.RefObject - - + + + + + ); }; @@ -151,7 +151,7 @@ const ConfigureSSOSteps = () => { ); }; -const ConfigureSSOCardProtect = ({ children }: { children: React.ReactNode }) => { +const ConfigureSSOProtect = ({ children }: { children: React.ReactNode }) => { const { session } = useSession(); const isPersonalWorkspace = !session?.lastActiveOrganizationId; const canManageEnterpriseConnections = useProtect( diff --git a/packages/ui/src/components/ConfigureSSO/__tests__/ConfigureSSO.test.tsx b/packages/ui/src/components/ConfigureSSO/__tests__/ConfigureSSO.test.tsx index 95c79396819..55f7d53cf28 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 f609259d947..314f410c077 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,17 @@ 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..ca506f95b28 --- /dev/null +++ b/packages/ui/src/components/OrganizationProfile/OrganizationSelfServeSSOPage.tsx @@ -0,0 +1,16 @@ +import { useOrganization } from '@clerk/shared/react'; +import { useRef } from 'react'; + +import { ConfigureSSOContent } 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 + 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..1b97d664ecf 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'); @@ -476,6 +477,97 @@ describe('OrganizationProfile', () => { }); }); + 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.withOrganizations(); + f.withUser({ + email_addresses: ['test@clerk.com'], + organization_memberships: [ + { + name: 'Org1', + self_serve_sso_enabled: true, + permissions: ['org:sys_entconns:manage'], + }, + ], + }); + }); + + render(, { wrapper }); + expect(await screen.findByText('Single Sign-On (SSO)')).toBeDefined(); + }); + + it('does not include SSO when disabled at the instance level', async () => { + const { wrapper } = await createFixtures(f => { + 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'], + }, + ], + }); + }); + + const { queryByText } = render(, { wrapper }); + 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 () => { + 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: false, + permissions: ['org:sys_entconns:manage'], + }, + ], + }); + }); + + const { queryByText } = render(, { wrapper }); + await waitFor(() => expect(queryByText('Single Sign-On (SSO)')).toBeNull()); + }); + + it('includes SSO even when the user does not have the manage enterprise connections permission, but the page surfaces a warning', async () => { + const { wrapper, fixtures } = 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: [], + }, + ], + }); + }); + + fixtures.clerk.user?.getEnterpriseConnections.mockResolvedValue([]); + + render(, { wrapper }); + expect(await screen.findByText('Single Sign-On (SSO)')).toBeDefined(); + + cleanup(); + render(, { wrapper }); + expect(await screen.findByText(/you do not have permission to manage single sign-on/i)).toBeDefined(); + expect( + screen.queryByText(/contact your organization.*administrator to upgrade your permissions/i), + ).toBeInTheDocument(); + }); + }); + 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..410f39bd0c4 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..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'; @@ -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); @@ -35,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.'); @@ -56,9 +59,19 @@ export const useOrganizationProfileContext = (): OrganizationProfileContextType // The C2 had a subscription in the past Boolean(statements.data.length > 0); + 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 = () => @@ -68,6 +81,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 +95,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/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 d3a03b782df..f6e6d2d3490 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[]; }; @@ -77,6 +86,7 @@ export const createOrganizationProfileCustomPages = ( clerk: LoadedClerk, shouldShowBilling: boolean, environment?: EnvironmentResource, + shouldShowSelfServeSSO = false, ) => { return createCustomPages( { @@ -89,6 +99,7 @@ export const createOrganizationProfileCustomPages = ( shouldShowBilling, environment, true, + shouldShowSelfServeSSO, ); }; @@ -98,6 +109,7 @@ const createCustomPages = ( shouldShowBilling: boolean, environment?: EnvironmentResource, organization?: boolean, + shouldShowSelfServeSSO = false, ) => { const { INITIAL_ROUTES, pageToRootNavbarRouteMap, validReorderItemLabels } = getDefaultRoutes({ commerce: organization @@ -106,6 +118,7 @@ const createCustomPages = ( apiKeys: organization ? !disabledOrganizationAPIKeysFeature(clerk, environment) : !disabledUserAPIKeysFeature(clerk, environment), + selfServeSSO: organization ? shouldShowSelfServeSSO && !disabledSelfServeSSOFeature(clerk, environment) : false, }); if (isDevelopmentSDK(clerk)) { @@ -315,9 +328,11 @@ const getUserProfileDefaultRoutes = ({ const getOrganizationProfileDefaultRoutes = ({ commerce, apiKeys, + selfServeSSO, }: { commerce: boolean; apiKeys: boolean; + selfServeSSO: boolean; }): GetDefaultRoutesReturnType => { const INITIAL_ROUTES: NavbarRoute[] = [ { @@ -349,6 +364,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,