From 7cc0576749ef56ec73d637a085cebcc75e77aecb Mon Sep 17 00:00:00 2001 From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com> Date: Thu, 16 Jan 2025 18:05:45 -0300 Subject: [PATCH] Support `enterprise_sso` strategy --- .changeset/quiet-dingos-laugh.md | 6 +++ .../machines/sign-in/router.machine.ts | 2 +- .../machines/sign-up/router.machine.ts | 16 ++++++++ .../internals/machines/sign-up/start.types.ts | 9 ++++- .../internals/machines/types/router.types.ts | 8 +++- .../elements/src/react/common/connections.tsx | 4 +- .../elements/src/react/common/loading.tsx | 4 +- .../hooks/use-third-party-provider.hook.ts | 37 +++++++++++++------ .../src/react/utils/map-scope-to-strategy.ts | 8 +++- .../src/utils/third-party-strategies.ts | 9 ++++- packages/types/src/signUp.ts | 2 +- 11 files changed, 80 insertions(+), 25 deletions(-) create mode 100644 .changeset/quiet-dingos-laugh.md diff --git a/.changeset/quiet-dingos-laugh.md b/.changeset/quiet-dingos-laugh.md new file mode 100644 index 00000000000..9579b57e8cf --- /dev/null +++ b/.changeset/quiet-dingos-laugh.md @@ -0,0 +1,6 @@ +--- +'@clerk/elements': patch +'@clerk/types': patch +--- + +Support `enterprise_sso` strategy (SAML, OIDC, EASIE) on custom flows with `@clerk/elements` diff --git a/packages/elements/src/internals/machines/sign-in/router.machine.ts b/packages/elements/src/internals/machines/sign-in/router.machine.ts index 9c556a8bcd7..ecd0446b29d 100644 --- a/packages/elements/src/internals/machines/sign-in/router.machine.ts +++ b/packages/elements/src/internals/machines/sign-in/router.machine.ts @@ -204,7 +204,7 @@ export const SignInRouterMachine = setup({ }, })), }, - 'AUTHENTICATE.SAML': { + 'AUTHENTICATE.ENTERPRISE_SSO': { actions: sendTo(ThirdPartyMachineId, ({ context }) => ({ type: 'REDIRECT', params: { diff --git a/packages/elements/src/internals/machines/sign-up/router.machine.ts b/packages/elements/src/internals/machines/sign-up/router.machine.ts index 49b6f89e9fc..e04bb2f66a1 100644 --- a/packages/elements/src/internals/machines/sign-up/router.machine.ts +++ b/packages/elements/src/internals/machines/sign-up/router.machine.ts @@ -220,6 +220,22 @@ export const SignUpRouterMachine = setup({ }, })), }, + 'AUTHENTICATE.ENTERPRISE_SSO': { + actions: sendTo(ThirdPartyMachineId, ({ context }) => ({ + type: 'REDIRECT', + params: { + strategy: 'enterprise_sso', + emailAddress: context.formRef.getSnapshot().context.fields.get('emailAddress')?.value, + redirectUrl: `${ + context.router?.mode === ROUTING.virtual + ? context.clerk.__unstable__environment?.displayConfig.signUpUrl + : context.router?.basePath + }${SSO_CALLBACK_PATH_ROUTE}`, + redirectUrlComplete: + context.router?.searchParams().get('redirect_url') || context.clerk.buildAfterSignUpUrl(), + }, + })), + }, 'AUTHENTICATE.WEB3': { actions: sendTo('start', ({ event }) => event), }, diff --git a/packages/elements/src/internals/machines/sign-up/start.types.ts b/packages/elements/src/internals/machines/sign-up/start.types.ts index 435db4cd3df..edbaec7b389 100644 --- a/packages/elements/src/internals/machines/sign-up/start.types.ts +++ b/packages/elements/src/internals/machines/sign-up/start.types.ts @@ -1,5 +1,5 @@ import type { ClerkAPIResponseError } from '@clerk/shared/error'; -import type { OAuthStrategy, SamlStrategy, Web3Strategy } from '@clerk/types'; +import type { EnterpriseSSOStrategy, OAuthStrategy, SamlStrategy, Web3Strategy } from '@clerk/types'; import type { ActorRefFrom, ErrorActorEvent } from 'xstate'; import type { FormMachine } from '~/internals/machines/form'; @@ -18,12 +18,17 @@ export type SignUpStartSubmitEvent = { type: 'SUBMIT'; action: 'submit' }; // TODO: Consolidate with SignInStartMachine export type SignUpStartRedirectOauthEvent = { type: 'AUTHENTICATE.OAUTH'; strategy: OAuthStrategy }; export type SignUpStartRedirectSamlEvent = { type: 'AUTHENTICATE.SAML'; strategy?: SamlStrategy }; +export type SignUpStartRedirectEnterpriseSSOEvent = { + type: 'AUTHENTICATE.ENTERPRISE_SSO'; + strategy?: EnterpriseSSOStrategy; +}; export type SignUpStartRedirectWeb3Event = { type: 'AUTHENTICATE.WEB3'; strategy: Web3Strategy }; export type SignUpStartRedirectEvent = | SignUpStartRedirectOauthEvent | SignUpStartRedirectSamlEvent - | SignUpStartRedirectWeb3Event; + | SignUpStartRedirectWeb3Event + | SignUpStartRedirectEnterpriseSSOEvent; export type SignUpStartEvents = ErrorActorEvent | SignUpStartSubmitEvent | SignUpStartRedirectEvent | SetFormEvent; diff --git a/packages/elements/src/internals/machines/types/router.types.ts b/packages/elements/src/internals/machines/types/router.types.ts index fa32861bf87..89bd93ee25d 100644 --- a/packages/elements/src/internals/machines/types/router.types.ts +++ b/packages/elements/src/internals/machines/types/router.types.ts @@ -1,6 +1,7 @@ import type { ClerkRouter } from '@clerk/shared/router'; import type { ClerkResource, + EnterpriseSSOStrategy, LoadedClerk, OAuthStrategy, SamlStrategy, @@ -46,13 +47,18 @@ export type BaseRouterLoadingEvent = ( export type BaseRouterRedirectOauthEvent = { type: 'AUTHENTICATE.OAUTH'; strategy: OAuthStrategy }; export type BaseRouterRedirectSamlEvent = { type: 'AUTHENTICATE.SAML'; strategy?: SamlStrategy }; +export type BaseRouterRedirectEnterpriseSSOEvent = { + type: 'AUTHENTICATE.ENTERPRISE_SSO'; + strategy?: EnterpriseSSOStrategy; +}; export type BaseRouterRedirectWeb3Event = { type: 'AUTHENTICATE.WEB3'; strategy: Web3Strategy }; export type BaseRouterSetClerkEvent = { type: 'CLERK.SET'; clerk: LoadedClerk }; export type BaseRouterRedirectEvent = | BaseRouterRedirectOauthEvent | BaseRouterRedirectSamlEvent - | BaseRouterRedirectWeb3Event; + | BaseRouterRedirectWeb3Event + | BaseRouterRedirectEnterpriseSSOEvent; // ---------------------------------- Input ---------------------------------- // diff --git a/packages/elements/src/react/common/connections.tsx b/packages/elements/src/react/common/connections.tsx index 0f949337741..e8aff0546b5 100644 --- a/packages/elements/src/react/common/connections.tsx +++ b/packages/elements/src/react/common/connections.tsx @@ -1,4 +1,4 @@ -import type { OAuthProvider, SamlStrategy, Web3Provider } from '@clerk/types'; +import type { EnterpriseSSOStrategy, OAuthProvider, SamlStrategy, Web3Provider } from '@clerk/types'; import { Slot } from '@radix-ui/react-slot'; import { createContext, useContext } from 'react'; @@ -29,7 +29,7 @@ export const useConnectionContext = () => { export interface ConnectionProps extends React.ButtonHTMLAttributes { asChild?: boolean; - name: OAuthProvider | Web3Provider | SamlStrategy; + name: OAuthProvider | Web3Provider | SamlStrategy | EnterpriseSSOStrategy; } /** diff --git a/packages/elements/src/react/common/loading.tsx b/packages/elements/src/react/common/loading.tsx index d9937cf8930..f26bcd11712 100644 --- a/packages/elements/src/react/common/loading.tsx +++ b/packages/elements/src/react/common/loading.tsx @@ -1,6 +1,6 @@ import { useClerk } from '@clerk/shared/react'; import { eventComponentMounted } from '@clerk/shared/telemetry'; -import type { OAuthProvider, SamlStrategy, Web3Provider } from '@clerk/types'; +import type { EnterpriseSSOStrategy, OAuthProvider, SamlStrategy, Web3Provider } from '@clerk/types'; import { useSelector } from '@xstate/react'; import * as React from 'react'; @@ -15,7 +15,7 @@ import type { TSignUpStep } from '~/react/sign-up/step'; import { SIGN_UP_STEPS } from '~/react/sign-up/step'; import { isProviderStrategyScope, mapScopeToStrategy } from '~/react/utils/map-scope-to-strategy'; -type Strategy = OAuthProvider | SamlStrategy | Web3Provider; +type Strategy = OAuthProvider | SamlStrategy | EnterpriseSSOStrategy | Web3Provider; type LoadingScope = | 'global' | `step:${T}` diff --git a/packages/elements/src/react/hooks/use-third-party-provider.hook.ts b/packages/elements/src/react/hooks/use-third-party-provider.hook.ts index 820dd4f4ab2..7f0e6f9b450 100644 --- a/packages/elements/src/react/hooks/use-third-party-provider.hook.ts +++ b/packages/elements/src/react/hooks/use-third-party-provider.hook.ts @@ -1,5 +1,5 @@ import { useClerk } from '@clerk/shared/react'; -import type { OAuthProvider, SamlStrategy, Web3Provider } from '@clerk/types'; +import type { EnterpriseSSOStrategy, OAuthProvider, SamlStrategy, Web3Provider } from '@clerk/types'; import type React from 'react'; import { useCallback } from 'react'; import type { ActorRef } from 'xstate'; @@ -11,12 +11,15 @@ import type { UseThirdPartyProviderReturn } from '~/react/common/connections'; import { getEnabledThirdPartyProviders, isAuthenticatableOauthStrategy, + isEnterpriseSSOStrategy, isSamlStrategy, isWeb3Strategy, providerToDisplayData, } from '~/utils/third-party-strategies'; -const useIsProviderEnabled = (provider: OAuthProvider | Web3Provider | SamlStrategy): boolean | null => { +const useIsProviderEnabled = ( + provider: OAuthProvider | Web3Provider | SamlStrategy | EnterpriseSSOStrategy, +): boolean | null => { const clerk = useClerk(); // null indicates we don't know for sure @@ -24,8 +27,12 @@ const useIsProviderEnabled = (provider: OAuthProvider | Web3Provider | SamlStrat return null; } - if (provider === 'saml') { - return clerk.__unstable__environment?.userSettings.saml.enabled ?? false; + if (provider === 'saml' || provider === 'enterprise_sso') { + return ( + clerk.__unstable__environment?.userSettings.saml.enabled ?? + clerk.__unstable__environment?.userSettings.enterpriseSSO.enabled ?? + false + ); } const data = getEnabledThirdPartyProviders(clerk.__unstable__environment); @@ -40,16 +47,18 @@ export const useThirdPartyProvider = < TActor extends ActorRef | ActorRef, >( ref: TActor, - provider: OAuthProvider | Web3Provider | SamlStrategy, + provider: OAuthProvider | Web3Provider | SamlStrategy | EnterpriseSSOStrategy, ): UseThirdPartyProviderReturn => { const isProviderEnabled = useIsProviderEnabled(provider); const isSaml = isSamlStrategy(provider); - const details = isSaml - ? { - name: 'SAML', - strategy: 'saml' as SamlStrategy, - } - : providerToDisplayData[provider]; + const isEnterpriseSSO = isEnterpriseSSOStrategy(provider); + const details = + isEnterpriseSSO || isSaml + ? { + name: 'SSO', + strategy: provider, + } + : providerToDisplayData[provider]; const authenticate = useCallback( (event: React.MouseEvent) => { @@ -63,6 +72,10 @@ export const useThirdPartyProvider = < return ref.send({ type: 'AUTHENTICATE.SAML' }); } + if (isEnterpriseSSO) { + return ref.send({ type: 'AUTHENTICATE.ENTERPRISE_SSO' }); + } + if (provider === 'metamask') { return ref.send({ type: 'AUTHENTICATE.WEB3', strategy: 'web3_metamask_signature' }); } @@ -77,7 +90,7 @@ export const useThirdPartyProvider = < return ref.send({ type: 'AUTHENTICATE.OAUTH', strategy: `oauth_${provider}` }); }, - [provider, isProviderEnabled, isSaml, ref], + [provider, isProviderEnabled, isSaml, isEnterpriseSSO, ref], ); if (isProviderEnabled === false) { diff --git a/packages/elements/src/react/utils/map-scope-to-strategy.ts b/packages/elements/src/react/utils/map-scope-to-strategy.ts index fc3f2b9d7b1..bd170d46277 100644 --- a/packages/elements/src/react/utils/map-scope-to-strategy.ts +++ b/packages/elements/src/react/utils/map-scope-to-strategy.ts @@ -1,6 +1,6 @@ -import type { OAuthProvider, SamlStrategy, SignInStrategy, Web3Provider } from '@clerk/types'; +import type { EnterpriseSSOStrategy, OAuthProvider, SamlStrategy, SignInStrategy, Web3Provider } from '@clerk/types'; -type Strategy = OAuthProvider | SamlStrategy | Web3Provider; +type Strategy = OAuthProvider | SamlStrategy | EnterpriseSSOStrategy | Web3Provider; export function isProviderStrategyScope(value: string): value is Strategy { return value.startsWith('provider:'); @@ -21,6 +21,10 @@ export function mapScopeToStrategy(scope: T): return 'saml'; } + if (scope === 'provider:enterprise_sso') { + return 'enterprise_sso'; + } + const scopeWithoutPrefix = scope.replace('provider:', '') as OAuthProvider; return `oauth_${scopeWithoutPrefix}`; diff --git a/packages/elements/src/utils/third-party-strategies.ts b/packages/elements/src/utils/third-party-strategies.ts index eabd4044407..376dd92c43e 100644 --- a/packages/elements/src/utils/third-party-strategies.ts +++ b/packages/elements/src/utils/third-party-strategies.ts @@ -4,6 +4,7 @@ import { iconImageUrl } from '@clerk/shared/constants'; import { OAUTH_PROVIDERS } from '@clerk/shared/oauth'; import { WEB3_PROVIDERS } from '@clerk/shared/web3'; import type { + EnterpriseSSOStrategy, EnvironmentResource, OAuthProvider, OAuthStrategy, @@ -21,7 +22,7 @@ export type ThirdPartyStrategy = name: string; } | { - strategy: SamlStrategy; + strategy: SamlStrategy | EnterpriseSSOStrategy; iconUrl?: never; name: string; }; @@ -33,7 +34,7 @@ export type ThirdPartyProvider = name: string; } | { - strategy: SamlStrategy; + strategy: SamlStrategy | EnterpriseSSOStrategy; iconUrl?: never; name: string; }; @@ -72,6 +73,10 @@ export function isSamlStrategy(strategy: any): strategy is SamlStrategy { return strategy === 'saml'; } +export function isEnterpriseSSOStrategy(strategy: any): strategy is EnterpriseSSOStrategy { + return strategy === 'enterprise_sso'; +} + export function isWeb3Strategy( strategy: any, available: EnabledThirdPartyProviders['web3Strategies'], diff --git a/packages/types/src/signUp.ts b/packages/types/src/signUp.ts index 9498533a349..42148916210 100644 --- a/packages/types/src/signUp.ts +++ b/packages/types/src/signUp.ts @@ -130,7 +130,7 @@ export type PrepareVerificationParams = oidcLoginHint?: string; } | { - strategy: SamlStrategy; + strategy: SamlStrategy | EnterpriseSSOStrategy; redirectUrl?: string; actionCompleteRedirectUrl?: string; };