From 3c4be65dd572f91fc018690ea20be6e67723809c Mon Sep 17 00:00:00 2001 From: gabrielmeloc22 Date: Tue, 13 Oct 2026 10:20:53 -0300 Subject: [PATCH 1/4] fix: route to the session task when switching organization leaves the session pending Switching organization with setActive, as the OrganizationSwitcher does, only learns the session became pending from ... setActive now navigates to the task (taskUrls, or the sign-in page's task route) when an organization switch leaves t... --- .../clerk-js/src/core/__tests__/clerk.test.ts | 76 +++++++++++++++++++ packages/clerk-js/src/core/clerk.ts | 23 +++++- .../src/internal/clerk-js/sessionTasks.ts | 1 + packages/shared/src/types/session.ts | 2 +- 4 files changed, 97 insertions(+), 5 deletions(-) diff --git a/packages/clerk-js/src/core/__tests__/clerk.test.ts b/packages/clerk-js/src/core/__tests__/clerk.test.ts index 314c28a7ccf..accb203ce75 100644 --- a/packages/clerk-js/src/core/__tests__/clerk.test.ts +++ b/packages/clerk-js/src/core/__tests__/clerk.test.ts @@ -644,6 +644,82 @@ describe('Clerk singleton', () => { }); }); + describe('when switching organization leaves the session pending', () => { + const pendingTask = { key: 'setup-enterprise-sso' } as const; + const createSession = () => { + const session: any = { + id: '1', + remove: vi.fn(), + status: 'active', + user: {}, + getToken: vi.fn(), + lastActiveToken: { getRawString: () => 'mocked-token' }, + tasks: null, + currentTask: undefined, + }; + const becomePending = () => { + session.status = 'pending'; + session.tasks = [pendingTask]; + session.currentTask = pendingTask; + return Promise.resolve(); + }; + session.touch = vi.fn(becomePending); + session.__internal_touch = vi.fn(becomePending); + return session; + }; + + it('navigates to the task on the sign-in page instead of the redirect url', async () => { + const mockSession = createSession(); + mockClientFetch.mockReturnValue(Promise.resolve({ signedInSessions: [mockSession] })); + + const sut = new Clerk(productionPublishableKey); + sut.navigate = vi.fn(); + await sut.load({ signInUrl: '/sign-in' }); + await sut.setActive({ organization: 'org_id', redirectUrl: '/dashboard' }); + + expect(sut.navigate).toHaveBeenCalledTimes(1); + expect(vi.mocked(sut.navigate).mock.calls[0][0]).toContain('/sign-in#/tasks/setup-enterprise-sso'); + }); + + it('navigates to the task even without a redirect url', async () => { + const mockSession = createSession(); + mockClientFetch.mockReturnValue(Promise.resolve({ signedInSessions: [mockSession] })); + + const sut = new Clerk(productionPublishableKey); + sut.navigate = vi.fn(); + await sut.load({ signInUrl: '/sign-in' }); + await sut.setActive({ organization: 'org_id' }); + + expect(vi.mocked(sut.navigate).mock.calls[0][0]).toContain('/sign-in#/tasks/setup-enterprise-sso'); + }); + + it('navigates to the `taskUrls` option for the task', async () => { + const mockSession = createSession(); + mockClientFetch.mockReturnValue(Promise.resolve({ signedInSessions: [mockSession] })); + + const sut = new Clerk(productionPublishableKey); + sut.navigate = vi.fn(); + await sut.load({ taskUrls: { 'setup-enterprise-sso': '/sso' } }); + await sut.setActive({ organization: 'org_id' }); + + expect(sut.navigate).toHaveBeenCalledWith('/sso'); + }); + + it('leaves navigation to the `navigate` callback', async () => { + const mockSession = createSession(); + mockClientFetch.mockReturnValue(Promise.resolve({ signedInSessions: [mockSession] })); + const navigate = vi.fn(); + + const sut = new Clerk(productionPublishableKey); + sut.navigate = vi.fn(); + await sut.load({ signInUrl: '/sign-in' }); + await sut.setActive({ organization: 'org_id', navigate }); + + expect(navigate).toHaveBeenCalled(); + expect(sut.navigate).not.toHaveBeenCalled(); + }); + }); + describe('with force organization selection enabled', () => { const mockSession = { id: '1', diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index d124caf535e..2a3080dd6b1 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -120,6 +120,7 @@ import type { ResumeAfterProtectCheckParams, SDKMetadata, SessionResource, + SessionTask, SessionTouchParams, SetActiveParams, SignedInSessionResource, @@ -2011,7 +2012,7 @@ export class Clerk implements ClerkInterface { await onBeforeSetActive(newSession === null ? 'sign-out' : undefined); } - const taskUrl = + let taskUrl = newSession?.status === 'pending' && newSession?.currentTask && this.#options.taskUrls?.[newSession?.currentTask.key]; @@ -2061,6 +2062,16 @@ export class Clerk implements ClerkInterface { newSession = this.#getSessionFromClient(newSession?.id, updatedClient); } + if ( + !taskUrl && + !setActiveNavigate && + shouldSwitchOrganization && + newSession?.status === 'pending' && + newSession.currentTask + ) { + taskUrl = this.#buildTaskUrl(newSession.currentTask); + } + // getToken syncs __session and __client_uat to cookies using events.TokenUpdate dispatched event. try { const token = await newSession?.getToken(); @@ -2085,7 +2096,7 @@ export class Clerk implements ClerkInterface { // automatic reloading when reloading shouldn't be happening. const tracker = createBeforeUnloadTracker(this.#options.standardBrowser); - if (shouldNavigate) { + if (shouldNavigate || taskUrl) { await tracker.track(async () => { if (!this.client) { // Typescript is not happy because since thinks this.client might have changed to undefined because the function is asynchronous. @@ -2421,7 +2432,11 @@ export class Clerk implements ClerkInterface { return ''; } - const customTaskUrl = this.#options.taskUrls?.[currentTask.key]; + return this.#buildTaskUrl(currentTask, options); + } + + #buildTaskUrl(task: SessionTask, options?: TasksRedirectOptions): string { + const customTaskUrl = this.#options.taskUrls?.[task.key]; if (customTaskUrl) { return customTaskUrl; } @@ -2429,7 +2444,7 @@ export class Clerk implements ClerkInterface { return buildURL( { base: this.buildSignInUrl(options), - hashPath: getTaskEndpoint(currentTask), + hashPath: getTaskEndpoint(task), }, { stringify: true, diff --git a/packages/shared/src/internal/clerk-js/sessionTasks.ts b/packages/shared/src/internal/clerk-js/sessionTasks.ts index e0d0fd1e0f8..459025ee3ed 100644 --- a/packages/shared/src/internal/clerk-js/sessionTasks.ts +++ b/packages/shared/src/internal/clerk-js/sessionTasks.ts @@ -10,6 +10,7 @@ export const INTERNAL_SESSION_TASK_ROUTE_BY_KEY: Record Date: Tue, 13 Oct 2026 10:37:31 -0300 Subject: [PATCH 2/4] feat: add the setup-enterprise-sso session task to SignIn and SignUp Organizations can require members to authenticate through their enterprise SSO connection before using the organization, which the backend expresses as the setup-enterprise-sso session task. Clerk components had no step for it, so a pending session landed on an empty tasks route. The task step finds the active organization's SSO connection and starts the account linking flow through it, which also covers re-authenticating an already linked account. --- .changeset/setup-enterprise-sso-task.md | 8 + packages/localizations/src/ar-SA.ts | 10 ++ packages/localizations/src/be-BY.ts | 10 ++ packages/localizations/src/bg-BG.ts | 10 ++ packages/localizations/src/bn-IN.ts | 10 ++ packages/localizations/src/ca-ES.ts | 10 ++ packages/localizations/src/cs-CZ.ts | 10 ++ packages/localizations/src/da-DK.ts | 10 ++ packages/localizations/src/de-DE.ts | 10 ++ packages/localizations/src/el-GR.ts | 10 ++ packages/localizations/src/en-GB.ts | 10 ++ packages/localizations/src/en-US.ts | 10 ++ packages/localizations/src/es-CR.ts | 10 ++ packages/localizations/src/es-ES.ts | 10 ++ packages/localizations/src/es-MX.ts | 10 ++ packages/localizations/src/es-UY.ts | 10 ++ packages/localizations/src/fa-IR.ts | 10 ++ packages/localizations/src/fi-FI.ts | 10 ++ packages/localizations/src/fr-FR.ts | 10 ++ packages/localizations/src/he-IL.ts | 10 ++ packages/localizations/src/hi-IN.ts | 10 ++ packages/localizations/src/hr-HR.ts | 10 ++ packages/localizations/src/hu-HU.ts | 10 ++ packages/localizations/src/id-ID.ts | 10 ++ packages/localizations/src/is-IS.ts | 10 ++ packages/localizations/src/it-IT.ts | 10 ++ packages/localizations/src/ja-JP.ts | 10 ++ packages/localizations/src/kk-KZ.ts | 10 ++ packages/localizations/src/ko-KR.ts | 10 ++ packages/localizations/src/mn-MN.ts | 10 ++ packages/localizations/src/ms-MY.ts | 10 ++ packages/localizations/src/nb-NO.ts | 10 ++ packages/localizations/src/nl-BE.ts | 10 ++ packages/localizations/src/nl-NL.ts | 10 ++ packages/localizations/src/pl-PL.ts | 10 ++ packages/localizations/src/pt-BR.ts | 10 ++ packages/localizations/src/pt-PT.ts | 10 ++ packages/localizations/src/ro-RO.ts | 10 ++ packages/localizations/src/ru-RU.ts | 10 ++ packages/localizations/src/sk-SK.ts | 10 ++ packages/localizations/src/sr-RS.ts | 10 ++ packages/localizations/src/sv-SE.ts | 10 ++ packages/localizations/src/ta-IN.ts | 10 ++ packages/localizations/src/te-IN.ts | 10 ++ packages/localizations/src/th-TH.ts | 10 ++ packages/localizations/src/tr-TR.ts | 10 ++ packages/localizations/src/uk-UA.ts | 10 ++ packages/localizations/src/vi-VN.ts | 10 ++ packages/localizations/src/zh-CN.ts | 10 ++ packages/localizations/src/zh-TW.ts | 10 ++ packages/shared/src/types/localization.ts | 10 ++ .../ui/src/components/SessionTasks/index.tsx | 4 + .../__tests__/TaskSetupEnterpriseSso.test.tsx | 74 +++++++++ .../tasks/TaskSetupEnterpriseSso/index.tsx | 155 ++++++++++++++++++ packages/ui/src/elements/contexts/index.tsx | 3 +- 55 files changed, 743 insertions(+), 1 deletion(-) create mode 100644 .changeset/setup-enterprise-sso-task.md create mode 100644 packages/ui/src/components/SessionTasks/tasks/TaskSetupEnterpriseSso/__tests__/TaskSetupEnterpriseSso.test.tsx create mode 100644 packages/ui/src/components/SessionTasks/tasks/TaskSetupEnterpriseSso/index.tsx diff --git a/.changeset/setup-enterprise-sso-task.md b/.changeset/setup-enterprise-sso-task.md new file mode 100644 index 00000000000..61f2d62806f --- /dev/null +++ b/.changeset/setup-enterprise-sso-task.md @@ -0,0 +1,8 @@ +--- +'@clerk/shared': minor +'@clerk/clerk-js': minor +'@clerk/ui': minor +'@clerk/localizations': minor +--- + +Introduce the `setup-enterprise-sso` session task. When an organization requires its members to authenticate through its enterprise SSO connection, `` and `` now show a step that sends the user through that connection before the organization can be used. Switching to such an organization with `setActive` also routes to the task instead of leaving the session pending with nothing on screen. diff --git a/packages/localizations/src/ar-SA.ts b/packages/localizations/src/ar-SA.ts index cc7882eab1a..ae0fc9a16b4 100644 --- a/packages/localizations/src/ar-SA.ts +++ b/packages/localizations/src/ar-SA.ts @@ -2001,6 +2001,16 @@ export const arSA: LocalizationResource = { subtitle: undefined, title: undefined, }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: undefined, signOut: { diff --git a/packages/localizations/src/be-BY.ts b/packages/localizations/src/be-BY.ts index c95417dc549..ddc6334fd53 100644 --- a/packages/localizations/src/be-BY.ts +++ b/packages/localizations/src/be-BY.ts @@ -2010,6 +2010,16 @@ export const beBY: LocalizationResource = { subtitle: undefined, title: undefined, }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: undefined, signOut: { diff --git a/packages/localizations/src/bg-BG.ts b/packages/localizations/src/bg-BG.ts index a2e222547eb..5a379a59591 100644 --- a/packages/localizations/src/bg-BG.ts +++ b/packages/localizations/src/bg-BG.ts @@ -2006,6 +2006,16 @@ export const bgBG: LocalizationResource = { subtitle: undefined, title: undefined, }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: undefined, signOut: { diff --git a/packages/localizations/src/bn-IN.ts b/packages/localizations/src/bn-IN.ts index 2cb0e9dec42..dccee81a651 100644 --- a/packages/localizations/src/bn-IN.ts +++ b/packages/localizations/src/bn-IN.ts @@ -2016,6 +2016,16 @@ export const bnIN: LocalizationResource = { subtitle: 'আপনি চালিয়ে যাওয়ার আগে আপনার অ্যাকাউন্টের একটি নতুন পাসওয়ার্ড প্রয়োজন', title: 'আপনার পাসওয়ার্ড রিসেট করুন', }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: 'দুই-ধাপ যাচাইকরণ সেটআপ', signOut: { diff --git a/packages/localizations/src/ca-ES.ts b/packages/localizations/src/ca-ES.ts index fd6720cff57..831ff6dd9a2 100644 --- a/packages/localizations/src/ca-ES.ts +++ b/packages/localizations/src/ca-ES.ts @@ -2014,6 +2014,16 @@ export const caES: LocalizationResource = { subtitle: undefined, title: undefined, }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: undefined, signOut: { diff --git a/packages/localizations/src/cs-CZ.ts b/packages/localizations/src/cs-CZ.ts index e025627f5bc..bb957c33d14 100644 --- a/packages/localizations/src/cs-CZ.ts +++ b/packages/localizations/src/cs-CZ.ts @@ -2016,6 +2016,16 @@ export const csCZ: LocalizationResource = { subtitle: undefined, title: undefined, }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: undefined, signOut: { diff --git a/packages/localizations/src/da-DK.ts b/packages/localizations/src/da-DK.ts index 6e0d81a20b2..ddcdb4161f4 100644 --- a/packages/localizations/src/da-DK.ts +++ b/packages/localizations/src/da-DK.ts @@ -2002,6 +2002,16 @@ export const daDK: LocalizationResource = { subtitle: undefined, title: undefined, }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: undefined, signOut: { diff --git a/packages/localizations/src/de-DE.ts b/packages/localizations/src/de-DE.ts index 4bc781d2353..a98d27fb520 100644 --- a/packages/localizations/src/de-DE.ts +++ b/packages/localizations/src/de-DE.ts @@ -2023,6 +2023,16 @@ export const deDE: LocalizationResource = { subtitle: undefined, title: undefined, }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: undefined, signOut: { diff --git a/packages/localizations/src/el-GR.ts b/packages/localizations/src/el-GR.ts index 8bf87a2b91f..75079f78e8a 100644 --- a/packages/localizations/src/el-GR.ts +++ b/packages/localizations/src/el-GR.ts @@ -2016,6 +2016,16 @@ export const elGR: LocalizationResource = { subtitle: 'Για λόγους ασφαλείας, παρακαλώ επαναφέρετε τον κωδικό πρόσβασής σας', title: 'Επαναφορά κωδικού πρόσβασης', }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: undefined, signOut: { diff --git a/packages/localizations/src/en-GB.ts b/packages/localizations/src/en-GB.ts index 0dfddf53585..2b0ef6d7912 100644 --- a/packages/localizations/src/en-GB.ts +++ b/packages/localizations/src/en-GB.ts @@ -2006,6 +2006,16 @@ export const enGB: LocalizationResource = { subtitle: undefined, title: undefined, }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: undefined, signOut: { diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts index 7e862ea758e..153e9a097a0 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -2053,6 +2053,16 @@ export const enUS: LocalizationResource = { subtitle: 'Your account requires a new password before you can continue', title: 'Reset your password', }, + taskSetupEnterpriseSso: { + connectionUnavailable: "Your organization's SSO connection isn't available. Contact your administrator.", + formButtonPrimary: 'Continue with {{connectionName}}', + signOut: { + actionLink: 'Sign out', + actionText: 'Signed in as {{identifier}}', + }, + subtitle: '{{organizationName}} requires you to sign in with your organization account to continue.', + title: 'Continue with SSO', + }, taskSetupMfa: { badge: 'Two-step verification setup', signOut: { diff --git a/packages/localizations/src/es-CR.ts b/packages/localizations/src/es-CR.ts index 0db7fbb5555..b26942b780f 100644 --- a/packages/localizations/src/es-CR.ts +++ b/packages/localizations/src/es-CR.ts @@ -2013,6 +2013,16 @@ export const esCR: LocalizationResource = { subtitle: undefined, title: undefined, }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: undefined, signOut: { diff --git a/packages/localizations/src/es-ES.ts b/packages/localizations/src/es-ES.ts index c8f7d6d7b89..a68e08f1eb0 100644 --- a/packages/localizations/src/es-ES.ts +++ b/packages/localizations/src/es-ES.ts @@ -2014,6 +2014,16 @@ export const esES: LocalizationResource = { subtitle: undefined, title: undefined, }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: undefined, signOut: { diff --git a/packages/localizations/src/es-MX.ts b/packages/localizations/src/es-MX.ts index 713bc34b6c9..7b36948f0be 100644 --- a/packages/localizations/src/es-MX.ts +++ b/packages/localizations/src/es-MX.ts @@ -2014,6 +2014,16 @@ export const esMX: LocalizationResource = { subtitle: undefined, title: undefined, }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: undefined, signOut: { diff --git a/packages/localizations/src/es-UY.ts b/packages/localizations/src/es-UY.ts index 3e72c1ee734..6f8b8b1aae1 100644 --- a/packages/localizations/src/es-UY.ts +++ b/packages/localizations/src/es-UY.ts @@ -2013,6 +2013,16 @@ export const esUY: LocalizationResource = { subtitle: undefined, title: undefined, }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: undefined, signOut: { diff --git a/packages/localizations/src/fa-IR.ts b/packages/localizations/src/fa-IR.ts index a255fe5baf8..faa635d1290 100644 --- a/packages/localizations/src/fa-IR.ts +++ b/packages/localizations/src/fa-IR.ts @@ -2017,6 +2017,16 @@ export const faIR: LocalizationResource = { subtitle: undefined, title: undefined, }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: undefined, signOut: { diff --git a/packages/localizations/src/fi-FI.ts b/packages/localizations/src/fi-FI.ts index 7f2d2139608..b67c4b7f61a 100644 --- a/packages/localizations/src/fi-FI.ts +++ b/packages/localizations/src/fi-FI.ts @@ -2018,6 +2018,16 @@ export const fiFI: LocalizationResource = { subtitle: 'Tilisi vaatii uuden salasanan ennen kuin voit jatkaa', title: 'Nollaa salasanasi', }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: 'Kaksivaiheisen todennuksen asennus', signOut: { diff --git a/packages/localizations/src/fr-FR.ts b/packages/localizations/src/fr-FR.ts index 0c7d92c392e..5aa5de46ffe 100644 --- a/packages/localizations/src/fr-FR.ts +++ b/packages/localizations/src/fr-FR.ts @@ -2022,6 +2022,16 @@ export const frFR: LocalizationResource = { subtitle: undefined, title: undefined, }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: undefined, signOut: { diff --git a/packages/localizations/src/he-IL.ts b/packages/localizations/src/he-IL.ts index a27db252446..9e37c3d72d0 100644 --- a/packages/localizations/src/he-IL.ts +++ b/packages/localizations/src/he-IL.ts @@ -1995,6 +1995,16 @@ export const heIL: LocalizationResource = { subtitle: undefined, title: undefined, }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: undefined, signOut: { diff --git a/packages/localizations/src/hi-IN.ts b/packages/localizations/src/hi-IN.ts index 78251d7e485..7834ec52d79 100644 --- a/packages/localizations/src/hi-IN.ts +++ b/packages/localizations/src/hi-IN.ts @@ -2017,6 +2017,16 @@ export const hiIN: LocalizationResource = { subtitle: 'जारी रखने से पहले आपके खाते को एक नए पासवर्ड की आवश्यकता है', title: 'अपना पासवर्ड रीसेट करें', }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: 'दो-चरणीय सत्यापन सेटअप', signOut: { diff --git a/packages/localizations/src/hr-HR.ts b/packages/localizations/src/hr-HR.ts index d0976af81e5..bc2a9b71577 100644 --- a/packages/localizations/src/hr-HR.ts +++ b/packages/localizations/src/hr-HR.ts @@ -2019,6 +2019,16 @@ export const hrHR: LocalizationResource = { subtitle: 'Vaš račun zahtijeva novu lozinku prije nego što možete nastaviti', title: 'Resetirajte svoju lozinku', }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: 'Postavljanje dvostupanjske verifikacije', signOut: { diff --git a/packages/localizations/src/hu-HU.ts b/packages/localizations/src/hu-HU.ts index eb5592c2763..a8ad150d5cf 100644 --- a/packages/localizations/src/hu-HU.ts +++ b/packages/localizations/src/hu-HU.ts @@ -2021,6 +2021,16 @@ export const huHU: LocalizationResource = { subtitle: 'A fiókodhoz új jelszó szükséges, mielőtt folytathatnád', title: 'Jelszó visszaállítása', }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: 'Kétlépcsős azonosítás beállítása', signOut: { diff --git a/packages/localizations/src/id-ID.ts b/packages/localizations/src/id-ID.ts index e17e483497b..84c23a70436 100644 --- a/packages/localizations/src/id-ID.ts +++ b/packages/localizations/src/id-ID.ts @@ -2012,6 +2012,16 @@ export const idID: LocalizationResource = { subtitle: undefined, title: undefined, }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: undefined, signOut: { diff --git a/packages/localizations/src/is-IS.ts b/packages/localizations/src/is-IS.ts index f468b8838d9..1089a60bcf9 100644 --- a/packages/localizations/src/is-IS.ts +++ b/packages/localizations/src/is-IS.ts @@ -2019,6 +2019,16 @@ export const isIS: LocalizationResource = { subtitle: 'Reikningurinn þinn krefst nýs lykilorðs áður en þú getur haldið áfram', title: 'Endurstilla lykilorðið þitt', }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: 'Uppsetning tveggja þrepa auðkenningar', signOut: { diff --git a/packages/localizations/src/it-IT.ts b/packages/localizations/src/it-IT.ts index ac1f77d29c5..6b16de9cbd4 100644 --- a/packages/localizations/src/it-IT.ts +++ b/packages/localizations/src/it-IT.ts @@ -2014,6 +2014,16 @@ export const itIT: LocalizationResource = { subtitle: undefined, title: undefined, }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: undefined, signOut: { diff --git a/packages/localizations/src/ja-JP.ts b/packages/localizations/src/ja-JP.ts index 1fcea5854dc..3140a3dc68b 100644 --- a/packages/localizations/src/ja-JP.ts +++ b/packages/localizations/src/ja-JP.ts @@ -2017,6 +2017,16 @@ export const jaJP: LocalizationResource = { subtitle: '続行するには、アカウントの新しいパスワードを設定する必要があります', title: 'パスワードをリセット', }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: '二段階認証の設定', signOut: { diff --git a/packages/localizations/src/kk-KZ.ts b/packages/localizations/src/kk-KZ.ts index 70735f967e9..0665ebcc5ea 100644 --- a/packages/localizations/src/kk-KZ.ts +++ b/packages/localizations/src/kk-KZ.ts @@ -1997,6 +1997,16 @@ export const kkKZ: LocalizationResource = { subtitle: undefined, title: undefined, }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: undefined, signOut: { diff --git a/packages/localizations/src/ko-KR.ts b/packages/localizations/src/ko-KR.ts index 78896999f98..9f6bc3b81f9 100644 --- a/packages/localizations/src/ko-KR.ts +++ b/packages/localizations/src/ko-KR.ts @@ -2000,6 +2000,16 @@ export const koKR: LocalizationResource = { subtitle: '계속하려면 비밀번호를 새로 설정해야 해요', title: '비밀번호 재설정', }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: undefined, signOut: { diff --git a/packages/localizations/src/mn-MN.ts b/packages/localizations/src/mn-MN.ts index c1e2bed88b7..c1a9b06fcf7 100644 --- a/packages/localizations/src/mn-MN.ts +++ b/packages/localizations/src/mn-MN.ts @@ -2006,6 +2006,16 @@ export const mnMN: LocalizationResource = { subtitle: undefined, title: undefined, }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: undefined, signOut: { diff --git a/packages/localizations/src/ms-MY.ts b/packages/localizations/src/ms-MY.ts index 016bc8c0743..a759e6876e4 100644 --- a/packages/localizations/src/ms-MY.ts +++ b/packages/localizations/src/ms-MY.ts @@ -2022,6 +2022,16 @@ export const msMY: LocalizationResource = { subtitle: 'Akaun anda memerlukan kata laluan baharu sebelum anda boleh meneruskan', title: 'Tetapkan semula kata laluan anda', }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: 'Persediaan pengesahan dua langkah', signOut: { diff --git a/packages/localizations/src/nb-NO.ts b/packages/localizations/src/nb-NO.ts index 384b98fdd7b..439fa6750a2 100644 --- a/packages/localizations/src/nb-NO.ts +++ b/packages/localizations/src/nb-NO.ts @@ -2019,6 +2019,16 @@ export const nbNO: LocalizationResource = { subtitle: 'Kontoen din krever et nytt passord før du kan fortsette', title: 'Tilbakestill passordet ditt', }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: 'Oppsett av to-trinns verifisering', signOut: { diff --git a/packages/localizations/src/nl-BE.ts b/packages/localizations/src/nl-BE.ts index 1bf581ef397..5b58424ee6e 100644 --- a/packages/localizations/src/nl-BE.ts +++ b/packages/localizations/src/nl-BE.ts @@ -2006,6 +2006,16 @@ export const nlBE: LocalizationResource = { subtitle: undefined, title: undefined, }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: undefined, signOut: { diff --git a/packages/localizations/src/nl-NL.ts b/packages/localizations/src/nl-NL.ts index 2349d69abe6..48ed077bf0f 100644 --- a/packages/localizations/src/nl-NL.ts +++ b/packages/localizations/src/nl-NL.ts @@ -2006,6 +2006,16 @@ export const nlNL: LocalizationResource = { subtitle: undefined, title: undefined, }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: undefined, signOut: { diff --git a/packages/localizations/src/pl-PL.ts b/packages/localizations/src/pl-PL.ts index c6f75646e70..09d86350815 100644 --- a/packages/localizations/src/pl-PL.ts +++ b/packages/localizations/src/pl-PL.ts @@ -2010,6 +2010,16 @@ export const plPL: LocalizationResource = { subtitle: undefined, title: 'Zresetuj hasło', }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: undefined, signOut: { diff --git a/packages/localizations/src/pt-BR.ts b/packages/localizations/src/pt-BR.ts index 7138b2f1e6f..74f5d1c9df2 100644 --- a/packages/localizations/src/pt-BR.ts +++ b/packages/localizations/src/pt-BR.ts @@ -2019,6 +2019,16 @@ export const ptBR: LocalizationResource = { subtitle: 'Sua conta requer uma nova senha antes de continuar', title: 'Resetar senha', }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: undefined, signOut: { diff --git a/packages/localizations/src/pt-PT.ts b/packages/localizations/src/pt-PT.ts index f0659aa1e63..818cdd13ba4 100644 --- a/packages/localizations/src/pt-PT.ts +++ b/packages/localizations/src/pt-PT.ts @@ -2016,6 +2016,16 @@ export const ptPT: LocalizationResource = { subtitle: 'A sua conta requer uma nova palavra-passe antes de poder continuar', title: 'Reponha a sua palavra-passe', }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: 'Configuração da verificação em duas etapas', signOut: { diff --git a/packages/localizations/src/ro-RO.ts b/packages/localizations/src/ro-RO.ts index e9000c9055a..4edaf633f8a 100644 --- a/packages/localizations/src/ro-RO.ts +++ b/packages/localizations/src/ro-RO.ts @@ -2020,6 +2020,16 @@ export const roRO: LocalizationResource = { subtitle: undefined, title: undefined, }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: undefined, signOut: { diff --git a/packages/localizations/src/ru-RU.ts b/packages/localizations/src/ru-RU.ts index 73ba47d5a48..e0682fb7d62 100644 --- a/packages/localizations/src/ru-RU.ts +++ b/packages/localizations/src/ru-RU.ts @@ -2017,6 +2017,16 @@ export const ruRU: LocalizationResource = { subtitle: undefined, title: undefined, }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: undefined, signOut: { diff --git a/packages/localizations/src/sk-SK.ts b/packages/localizations/src/sk-SK.ts index 025e874ff30..dcf2f4c78e4 100644 --- a/packages/localizations/src/sk-SK.ts +++ b/packages/localizations/src/sk-SK.ts @@ -2010,6 +2010,16 @@ export const skSK: LocalizationResource = { subtitle: undefined, title: undefined, }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: undefined, signOut: { diff --git a/packages/localizations/src/sr-RS.ts b/packages/localizations/src/sr-RS.ts index 14e8a094adf..9b51008f385 100644 --- a/packages/localizations/src/sr-RS.ts +++ b/packages/localizations/src/sr-RS.ts @@ -2003,6 +2003,16 @@ export const srRS: LocalizationResource = { subtitle: undefined, title: undefined, }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: undefined, signOut: { diff --git a/packages/localizations/src/sv-SE.ts b/packages/localizations/src/sv-SE.ts index f411009e4f3..e302510d80e 100644 --- a/packages/localizations/src/sv-SE.ts +++ b/packages/localizations/src/sv-SE.ts @@ -2008,6 +2008,16 @@ export const svSE: LocalizationResource = { subtitle: undefined, title: undefined, }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: undefined, signOut: { diff --git a/packages/localizations/src/ta-IN.ts b/packages/localizations/src/ta-IN.ts index 076f86f9873..636caebeafd 100644 --- a/packages/localizations/src/ta-IN.ts +++ b/packages/localizations/src/ta-IN.ts @@ -2022,6 +2022,16 @@ export const taIN: LocalizationResource = { subtitle: 'நீங்கள் தொடரும் முன் உங்கள் கணக்கிற்கு புதிய கடவுச்சொல் தேவை', title: 'உங்கள் கடவுச்சொல்லை மீட்டமைக்கவும்', }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: 'இரு-படி சரிபார்ப்பு அமைப்பு', signOut: { diff --git a/packages/localizations/src/te-IN.ts b/packages/localizations/src/te-IN.ts index 8a4c8d6f7a2..b2416eb462c 100644 --- a/packages/localizations/src/te-IN.ts +++ b/packages/localizations/src/te-IN.ts @@ -2020,6 +2020,16 @@ export const teIN: LocalizationResource = { subtitle: 'మీరు కొనసాగడానికి ముందు మీ ఖాతాకు కొత్త పాస్‌వర్డ్ అవసరం', title: 'మీ పాస్‌వర్డ్‌ను రీసెట్ చేయండి', }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: 'రెండు-దశల ధృవీకరణ సెటప్', signOut: { diff --git a/packages/localizations/src/th-TH.ts b/packages/localizations/src/th-TH.ts index eebb4436919..581a9da7027 100644 --- a/packages/localizations/src/th-TH.ts +++ b/packages/localizations/src/th-TH.ts @@ -2005,6 +2005,16 @@ export const thTH: LocalizationResource = { subtitle: undefined, title: undefined, }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: undefined, signOut: { diff --git a/packages/localizations/src/tr-TR.ts b/packages/localizations/src/tr-TR.ts index e840c7c2b2c..85e17b71d8e 100644 --- a/packages/localizations/src/tr-TR.ts +++ b/packages/localizations/src/tr-TR.ts @@ -2006,6 +2006,16 @@ export const trTR: LocalizationResource = { subtitle: undefined, title: undefined, }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: undefined, signOut: { diff --git a/packages/localizations/src/uk-UA.ts b/packages/localizations/src/uk-UA.ts index bd77cc5dcd0..05a834458e5 100644 --- a/packages/localizations/src/uk-UA.ts +++ b/packages/localizations/src/uk-UA.ts @@ -2002,6 +2002,16 @@ export const ukUA: LocalizationResource = { subtitle: undefined, title: undefined, }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: undefined, signOut: { diff --git a/packages/localizations/src/vi-VN.ts b/packages/localizations/src/vi-VN.ts index 35e6662dc65..e186a17d952 100644 --- a/packages/localizations/src/vi-VN.ts +++ b/packages/localizations/src/vi-VN.ts @@ -2015,6 +2015,16 @@ export const viVN: LocalizationResource = { subtitle: 'Tài khoản của bạn yêu cầu mật khẩu mới trước khi bạn có thể tiếp tục', title: 'Đặt lại mật khẩu của bạn', }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: 'Thiết lập xác minh hai bước', signOut: { diff --git a/packages/localizations/src/zh-CN.ts b/packages/localizations/src/zh-CN.ts index 7ea16a28b01..8d0a1d93822 100644 --- a/packages/localizations/src/zh-CN.ts +++ b/packages/localizations/src/zh-CN.ts @@ -1991,6 +1991,16 @@ export const zhCN: LocalizationResource = { subtitle: undefined, title: undefined, }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: undefined, signOut: { diff --git a/packages/localizations/src/zh-TW.ts b/packages/localizations/src/zh-TW.ts index ee115cb297f..99a4ab840fa 100644 --- a/packages/localizations/src/zh-TW.ts +++ b/packages/localizations/src/zh-TW.ts @@ -1995,6 +1995,16 @@ export const zhTW: LocalizationResource = { subtitle: '您的帳戶需要新的密碼才能繼續', title: '重設您的密碼', }, + taskSetupEnterpriseSso: { + connectionUnavailable: undefined, + formButtonPrimary: undefined, + signOut: { + actionLink: undefined, + actionText: undefined, + }, + subtitle: undefined, + title: undefined, + }, taskSetupMfa: { badge: '兩步驟驗證設定', signOut: { diff --git a/packages/shared/src/types/localization.ts b/packages/shared/src/types/localization.ts index 78e90a86b4f..dfd6b20f478 100644 --- a/packages/shared/src/types/localization.ts +++ b/packages/shared/src/types/localization.ts @@ -2419,6 +2419,16 @@ export type __internal_LocalizationResource = { }; formButtonPrimary: LocalizationValue; }; + taskSetupEnterpriseSso: { + title: LocalizationValue; + subtitle: LocalizationValue<'organizationName'>; + formButtonPrimary: LocalizationValue<'connectionName'>; + connectionUnavailable: LocalizationValue; + signOut: { + actionLink: LocalizationValue; + actionText: LocalizationValue<'identifier'>; + }; + }; taskSetupMfa: { badge: LocalizationValue; start: { diff --git a/packages/ui/src/components/SessionTasks/index.tsx b/packages/ui/src/components/SessionTasks/index.tsx index 0672ff7ff6a..16e84f2798c 100644 --- a/packages/ui/src/components/SessionTasks/index.tsx +++ b/packages/ui/src/components/SessionTasks/index.tsx @@ -18,6 +18,7 @@ import { import { Route, Switch, useRouter } from '../../router'; import { TaskChooseOrganization } from './tasks/TaskChooseOrganization'; import { TaskResetPassword } from './tasks/TaskResetPassword'; +import { TaskSetupEnterpriseSSO } from './tasks/TaskSetupEnterpriseSso'; import { TaskSetupMFA } from './tasks/TaskSetupMfa'; const SessionTasksStart = () => { @@ -108,6 +109,9 @@ function SessionTasksRoutes(): JSX.Element { + + + diff --git a/packages/ui/src/components/SessionTasks/tasks/TaskSetupEnterpriseSso/__tests__/TaskSetupEnterpriseSso.test.tsx b/packages/ui/src/components/SessionTasks/tasks/TaskSetupEnterpriseSso/__tests__/TaskSetupEnterpriseSso.test.tsx new file mode 100644 index 00000000000..9113afdca20 --- /dev/null +++ b/packages/ui/src/components/SessionTasks/tasks/TaskSetupEnterpriseSso/__tests__/TaskSetupEnterpriseSso.test.tsx @@ -0,0 +1,74 @@ +import type { EnterpriseConnectionResource, ExternalAccountResource } from '@clerk/shared/types'; +import { describe, expect, it } from 'vitest'; + +import { bindCreateFixtures } from '@/test/create-fixtures'; +import { render, waitFor } from '@/test/utils'; +import { SessionTasksContext } from '@/ui/contexts/components/SessionTasks'; + +import { TaskSetupEnterpriseSSO } from '..'; + +const { createFixtures } = bindCreateFixtures('SignIn'); + +const ssoConnection = { + id: 'entc_uop', + name: 'University of Phoenix', + active: true, + allowOrganizationAccountLinking: true, + samlConnection: { id: 'samlc_uop' }, +} as unknown as EnterpriseConnectionResource; + +const renderTask = (wrapper: Parameters[1]['wrapper']) => + render( + + + , + { wrapper }, + ); + +const studentWithTask = { + email_addresses: ['bart@gmail.com'], + organization_memberships: [{ name: 'University of Phoenix', id: 'org_uop', role: 'basic_member' }], +}; + +describe('TaskSetupEnterpriseSSO', () => { + it('does not render without the session task', async () => { + const { wrapper } = await createFixtures(f => f.withUser(studentWithTask)); + + const { queryByRole } = renderTask(wrapper); + + expect(queryByRole('button', { name: /continue with/i })).not.toBeInTheDocument(); + }); + + it("starts SSO through the active organization's connection", async () => { + const { wrapper, fixtures } = await createFixtures(f => + f.withUser({ ...studentWithTask, tasks: [{ key: 'setup-enterprise-sso' }] }), + ); + const organization = fixtures.clerk.user?.organizationMemberships[0].organization; + organization?.getEnterpriseConnections.mockResolvedValue([ssoConnection]); + fixtures.clerk.user?.createExternalAccount.mockResolvedValue({} as ExternalAccountResource); + + const { findByRole, userEvent } = renderTask(wrapper); + await userEvent.click(await findByRole('button', { name: 'Continue with University of Phoenix' })); + + await waitFor(() => + expect(fixtures.clerk.user?.createExternalAccount).toHaveBeenCalledWith({ + enterpriseConnectionId: 'entc_uop', + redirectUrl: window.location.href, + }), + ); + }); + + it('explains when the organization has no SSO connection to continue with', async () => { + const { wrapper, fixtures } = await createFixtures(f => + f.withUser({ ...studentWithTask, tasks: [{ key: 'setup-enterprise-sso' }] }), + ); + fixtures.clerk.user?.organizationMemberships[0].organization.getEnterpriseConnections.mockResolvedValue([ + { ...ssoConnection, allowOrganizationAccountLinking: false } as EnterpriseConnectionResource, + ]); + + const { findByText, queryByRole } = renderTask(wrapper); + + expect(await findByText(/SSO connection isn't available/i)).toBeInTheDocument(); + expect(queryByRole('button', { name: /continue with/i })).not.toBeInTheDocument(); + }); +}); diff --git a/packages/ui/src/components/SessionTasks/tasks/TaskSetupEnterpriseSso/index.tsx b/packages/ui/src/components/SessionTasks/tasks/TaskSetupEnterpriseSso/index.tsx new file mode 100644 index 00000000000..89182feae99 --- /dev/null +++ b/packages/ui/src/components/SessionTasks/tasks/TaskSetupEnterpriseSso/index.tsx @@ -0,0 +1,155 @@ +import { useClerk, useReverification, useUser } from '@clerk/shared/react'; +import type { EnterpriseConnectionResource } from '@clerk/shared/types'; +import { useEffect, useState } from 'react'; + +import { useSignOutContext, withCoreSessionSwitchGuard } from '@/ui/contexts'; +import { Button, Col, descriptors, Flow, localizationKeys, Text } from '@/ui/customizables'; +import { Card } from '@/ui/elements/Card'; +import { useCardState, withCardStateProvider } from '@/ui/elements/contexts'; +import { Header } from '@/ui/elements/Header'; +import { LoadingCardContainer } from '@/ui/elements/LoadingCard'; +import { useMultipleSessions } from '@/ui/hooks/useMultipleSessions'; +import { handleError } from '@/ui/utils/errorHandler'; +import { clerkWindowNavigate } from '@/ui/utils/windowNavigate'; + +import { withTaskGuard } from '../shared'; + +const isSSORequiredConnection = (connection: EnterpriseConnectionResource) => + connection.active && connection.allowOrganizationAccountLinking && !!connection.samlConnection; + +const TaskSetupEnterpriseSSOInternal = () => { + const clerk = useClerk(); + const { user } = useUser(); + const card = useCardState(); + const { otherSessions } = useMultipleSessions({ user: clerk.user }); + const { navigateAfterSignOut, navigateAfterMultiSessionSingleSignOutUrl } = useSignOutContext(); + const [connection, setConnection] = useState(); + + const organization = user?.organizationMemberships.find( + membership => membership.organization.id === clerk.session?.lastActiveOrganizationId, + )?.organization; + + useEffect(() => { + if (!organization) { + setConnection(null); + return; + } + + organization + .getEnterpriseConnections() + .then(connections => setConnection(connections.find(isSSORequiredConnection) ?? null)) + .catch(err => { + setConnection(null); + handleError(err, [], card.setError); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [organization?.id]); + + const createExternalAccount = useReverification((enterpriseConnectionId: string) => + user?.createExternalAccount({ enterpriseConnectionId, redirectUrl: window.location.href }), + ); + + const continueWithSSO = (enterpriseConnectionId: string) => + card + .runAsync(async () => { + const externalAccount = await createExternalAccount(enterpriseConnectionId); + const redirectUrl = externalAccount?.verification?.externalVerificationRedirectURL; + if (redirectUrl) { + clerkWindowNavigate(clerk, redirectUrl); + } + }) + .catch(err => handleError(err, [], card.setError)); + + const handleSignOut = () => { + if (otherSessions.length === 0) { + return clerk.signOut(navigateAfterSignOut); + } + + return clerk.signOut(navigateAfterMultiSessionSingleSignOutUrl, { sessionId: clerk.session?.id }); + }; + + const identifier = clerk.user?.primaryEmailAddress?.emailAddress ?? clerk.user?.username; + + if (connection === undefined) { + return ( + + + + + + + ); + } + + return ( + + + + + + + + + {card.error} + + {connection ? ( +