Skip to content

Commit 79cdd1f

Browse files
authored
feat(ui): Self-serve SSO within OrganizationProfile (#8600)
1 parent bcf0e77 commit 79cdd1f

18 files changed

Lines changed: 220 additions & 32 deletions

File tree

.changeset/dull-plums-sleep.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@clerk/localizations': minor
3+
'@clerk/clerk-js': minor
4+
'@clerk/shared': minor
5+
'@clerk/ui': minor
6+
---
7+
8+
Display "Single Sign-on (SSO)" section in `OrganizationProfile` if self-serve SSO is enabled on the current active organization

packages/clerk-js/src/core/resources/Organization.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export class Organization extends BaseResource implements OrganizationResource {
4949
membersCount = 0;
5050
pendingInvitationsCount = 0;
5151
maxAllowedMemberships!: number;
52+
selfServeSSOEnabled = false;
5253

5354
constructor(data: OrganizationJSON | OrganizationJSONSnapshot) {
5455
super();
@@ -303,6 +304,7 @@ export class Organization extends BaseResource implements OrganizationResource {
303304
this.pendingInvitationsCount = data.pending_invitations_count || 0;
304305
this.maxAllowedMemberships = data.max_allowed_memberships || 0;
305306
this.adminDeleteEnabled = data.admin_delete_enabled || false;
307+
this.selfServeSSOEnabled = data.self_serve_sso_enabled || false;
306308
this.createdAt = unixEpochToDate(data.created_at);
307309
this.updatedAt = unixEpochToDate(data.updated_at);
308310
return this;
@@ -321,6 +323,7 @@ export class Organization extends BaseResource implements OrganizationResource {
321323
pending_invitations_count: this.pendingInvitationsCount,
322324
max_allowed_memberships: this.maxAllowedMemberships,
323325
admin_delete_enabled: this.adminDeleteEnabled,
326+
self_serve_sso_enabled: this.selfServeSSOEnabled,
324327
created_at: this.createdAt.getTime(),
325328
updated_at: this.updatedAt.getTime(),
326329
};

packages/clerk-js/src/core/resources/__tests__/Organization.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ describe('Organization', () => {
1919
admin_delete_enabled: true,
2020
max_allowed_memberships: 3,
2121
has_image: true,
22+
self_serve_sso_enabled: true,
2223
});
2324

2425
expect(organization).toMatchObject({
@@ -32,6 +33,7 @@ describe('Organization', () => {
3233
pendingInvitationsCount: 10,
3334
maxAllowedMemberships: 3,
3435
adminDeleteEnabled: true,
36+
selfServeSSOEnabled: true,
3537
createdAt: expect.any(Date),
3638
updatedAt: expect.any(Date),
3739
publicMetadata: {

packages/localizations/src/en-US.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,7 @@ export const enUS: LocalizationResource = {
794794
navbar: {
795795
apiKeys: 'API keys',
796796
billing: 'Billing',
797+
selfServeSSO: 'Single Sign-On (SSO)',
797798
description: 'Manage your organization.',
798799
general: 'General',
799800
members: 'Members',

packages/shared/src/types/json.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,7 @@ export interface OrganizationJSON extends ClerkResourceJSON {
393393
pending_invitations_count: number;
394394
admin_delete_enabled: boolean;
395395
max_allowed_memberships: number;
396+
self_serve_sso_enabled?: boolean;
396397
}
397398

398399
export interface OrganizationMembershipJSON extends ClerkResourceJSON {

packages/shared/src/types/localization.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,6 +1021,7 @@ export type __internal_LocalizationResource = {
10211021
members: LocalizationValue;
10221022
billing: LocalizationValue;
10231023
apiKeys: LocalizationValue;
1024+
selfServeSSO: LocalizationValue;
10241025
};
10251026
badge__unverified: LocalizationValue;
10261027
badge__automaticInvitation: LocalizationValue;

packages/shared/src/types/organization.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export interface OrganizationResource extends ClerkResource, BillingPayerMethods
4646
publicMetadata: OrganizationPublicMetadata;
4747
adminDeleteEnabled: boolean;
4848
maxAllowedMemberships: number;
49+
selfServeSSOEnabled: boolean;
4950
createdAt: Date;
5051
updatedAt: Date;
5152
update: (params: UpdateOrganizationParams) => Promise<OrganizationResource>;

packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -58,16 +58,14 @@ const AuthenticatedContent = withCoreUserGuard(() => {
5858
flex: 1,
5959
})}
6060
>
61-
<ConfigureSSOCardProtect>
62-
<ConfigureSSOCardContent contentRef={contentRef} />
63-
</ConfigureSSOCardProtect>
61+
<ConfigureSSOContent contentRef={contentRef} />
6462
</Col>
6563
</ConfigureSSONavbar>
6664
</ProfileCard.Root>
6765
);
6866
});
6967

70-
const ConfigureSSOCardContent = ({ contentRef }: { contentRef: React.RefObject<HTMLDivElement> }) => {
68+
export const ConfigureSSOContent = ({ contentRef }: { contentRef: React.RefObject<HTMLDivElement> }) => {
7169
const {
7270
data: enterpriseConnections,
7371
isLoading: isLoadingEnterpriseConnections,
@@ -86,16 +84,18 @@ const ConfigureSSOCardContent = ({ contentRef }: { contentRef: React.RefObject<H
8684
}
8785

8886
return (
89-
<ConfigureSSOProvider
90-
hasSuccessfulTestRun={hasSuccessfulTestRun}
91-
enterpriseConnection={enterpriseConnection}
92-
contentRef={contentRef}
93-
createEnterpriseConnection={createEnterpriseConnection}
94-
updateEnterpriseConnection={updateEnterpriseConnection}
95-
deleteEnterpriseConnection={deleteEnterpriseConnection}
96-
>
97-
<ConfigureSSOSteps />
98-
</ConfigureSSOProvider>
87+
<ConfigureSSOProtect>
88+
<ConfigureSSOProvider
89+
hasSuccessfulTestRun={hasSuccessfulTestRun}
90+
enterpriseConnection={enterpriseConnection}
91+
contentRef={contentRef}
92+
createEnterpriseConnection={createEnterpriseConnection}
93+
updateEnterpriseConnection={updateEnterpriseConnection}
94+
deleteEnterpriseConnection={deleteEnterpriseConnection}
95+
>
96+
<ConfigureSSOSteps />
97+
</ConfigureSSOProvider>
98+
</ConfigureSSOProtect>
9999
);
100100
};
101101

@@ -151,7 +151,7 @@ const ConfigureSSOSteps = () => {
151151
);
152152
};
153153

154-
const ConfigureSSOCardProtect = ({ children }: { children: React.ReactNode }) => {
154+
const ConfigureSSOProtect = ({ children }: { children: React.ReactNode }) => {
155155
const { session } = useSession();
156156
const isPersonalWorkspace = !session?.lastActiveOrganizationId;
157157
const canManageEnterpriseConnections = useProtect(

packages/ui/src/components/ConfigureSSO/__tests__/ConfigureSSO.test.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ describe('ConfigureSSO', () => {
1111
describe('within an organization', () => {
1212
it('shows a warning if the active organization membership lacks the manage enterprise connections permission', async () => {
1313
const { wrapper, fixtures } = await createFixtures(f => {
14-
f.withEnterpriseSso({ selfServeSso: true });
14+
f.withEnterpriseSso({ selfServeSSO: true });
1515
f.withEmailAddress();
1616
f.withOrganizations();
1717
f.withUser({
@@ -31,7 +31,7 @@ describe('ConfigureSSO', () => {
3131

3232
it('renders the wizard when the active organization membership has the manage enterprise connections permission', async () => {
3333
const { wrapper, fixtures } = await createFixtures(f => {
34-
f.withEnterpriseSso({ selfServeSso: true });
34+
f.withEnterpriseSso({ selfServeSSO: true });
3535
f.withEmailAddress();
3636
f.withOrganizations();
3737
f.withUser({
@@ -54,7 +54,7 @@ describe('ConfigureSSO', () => {
5454
describe('in a personal workspace', () => {
5555
it('renders the wizard without checking the manage enterprise connections permission', async () => {
5656
const { wrapper, fixtures } = await createFixtures(f => {
57-
f.withEnterpriseSso({ selfServeSso: true });
57+
f.withEnterpriseSso({ selfServeSSO: true });
5858
f.withEmailAddress();
5959
f.withUser({ email_addresses: ['test@clerk.com'] });
6060
});

packages/ui/src/components/OrganizationProfile/OrganizationProfileRoutes.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,22 @@ const OrganizationPaymentAttemptPage = lazy(() =>
3737
})),
3838
);
3939

40+
const OrganizationSelfServeSSOPage = lazy(() =>
41+
import(/* webpackChunkName: "op-self-serve-sso-page"*/ './OrganizationSelfServeSSOPage').then(module => ({
42+
default: module.OrganizationSelfServeSSOPage,
43+
})),
44+
);
45+
4046
export const OrganizationProfileRoutes = () => {
4147
const {
4248
pages,
4349
isMembersPageRoot,
4450
isGeneralPageRoot,
4551
isBillingPageRoot,
4652
isAPIKeysPageRoot,
53+
isSelfServeSsoPageRoot,
4754
shouldShowBilling,
55+
shouldShowSelfServeSSO,
4856
apiKeysProps,
4957
} = useOrganizationProfileContext();
5058

@@ -142,6 +150,17 @@ export const OrganizationProfileRoutes = () => {
142150
</Route>
143151
</Protect>
144152
)}
153+
{shouldShowSelfServeSSO ? (
154+
<Route path={isSelfServeSsoPageRoot ? undefined : 'organization-self-serve-sso'}>
155+
<Switch>
156+
<Route index>
157+
<Suspense fallback={''}>
158+
<OrganizationSelfServeSSOPage />
159+
</Suspense>
160+
</Route>
161+
</Switch>
162+
</Route>
163+
) : null}
145164
</Route>
146165
</Switch>
147166
);

0 commit comments

Comments
 (0)