From c701b8594e26456858736bdd1fa8fa22f7a6ab5a Mon Sep 17 00:00:00 2001 From: Nicolas Lopes Date: Thu, 24 Oct 2024 18:17:56 -0300 Subject: [PATCH] wip --- package-lock.json | 10 +-- .../src/core/resources/EmailAddress.ts | 43 +++++++++++ .../ui/components/UserProfile/EmailForm.tsx | 17 +++-- .../VerifyWithEnterpriseConnection.tsx | 71 +++++++++++++++++++ .../src/ui/components/UserProfile/utils.ts | 12 ++++ packages/clerk-js/src/ui/hooks/index.ts | 1 + .../ui/hooks/useEnterpriseConnectionLink.ts | 32 +++++++++ packages/types/src/emailAddress.ts | 19 ++++- packages/types/src/json.ts | 1 + packages/types/src/strategies.ts | 1 + packages/types/src/verification.ts | 9 +++ 11 files changed, 206 insertions(+), 10 deletions(-) create mode 100644 packages/clerk-js/src/ui/components/UserProfile/VerifyWithEnterpriseConnection.tsx create mode 100644 packages/clerk-js/src/ui/hooks/useEnterpriseConnectionLink.ts diff --git a/package-lock.json b/package-lock.json index 32f301758c..3de4f9f7fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13056,9 +13056,10 @@ } }, "node_modules/@tanstack/react-cross-context": { - "version": "1.60.0", + "version": "1.74.5", + "resolved": "https://registry.npmjs.org/@tanstack/react-cross-context/-/react-cross-context-1.74.5.tgz", + "integrity": "sha512-a4BoKe1umpt4mmol2fUc7S11ilYIrn60ysoLot0NE+BeRsML83MvgPsLTAx7h1fTp0gmLjtpMjjubIJ8GlDIQg==", "dev": true, - "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" @@ -13069,9 +13070,10 @@ } }, "node_modules/@tanstack/react-store": { - "version": "0.5.5", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/@tanstack/react-store/-/react-store-0.5.6.tgz", + "integrity": "sha512-SitIpS5jTj28DajjLpWbIX+YetmJL+6PRY0DKKiCGBKfYIqj3ryODQYF3jB3SNoR9ifUA/jFkqbJdBKFtWd+AQ==", "dev": true, - "license": "MIT", "dependencies": { "@tanstack/store": "0.5.5", "use-sync-external-store": "^1.2.2" diff --git a/packages/clerk-js/src/core/resources/EmailAddress.ts b/packages/clerk-js/src/core/resources/EmailAddress.ts index 24ba765394..c50fb127a8 100644 --- a/packages/clerk-js/src/core/resources/EmailAddress.ts +++ b/packages/clerk-js/src/core/resources/EmailAddress.ts @@ -2,11 +2,13 @@ import { Poller } from '@clerk/shared/poller'; import type { AttemptEmailAddressVerificationParams, CreateEmailLinkFlowReturn, + CreateEnterpriseConnectionLinkFlowReturn, EmailAddressJSON, EmailAddressResource, IdentificationLinkResource, PrepareEmailAddressVerificationParams, StartEmailLinkFlowParams, + StartEnterpriseConnectionLinkFlowParams, VerificationResource, } from '@clerk/types'; @@ -16,6 +18,7 @@ import { BaseResource, IdentificationLink, Verification } from './internal'; export class EmailAddress extends BaseResource implements EmailAddressResource { id!: string; emailAddress = ''; + matchesEnterpriseConnection = false; linkedTo: IdentificationLinkResource[] = []; verification!: VerificationResource; @@ -77,6 +80,45 @@ export class EmailAddress extends BaseResource implements EmailAddressResource { return { startEmailLinkFlow, cancelEmailLinkFlow: stop }; }; + createEnterpriseConnectionLinkFlow = (): CreateEnterpriseConnectionLinkFlowReturn< + StartEnterpriseConnectionLinkFlowParams, + EmailAddressResource + > => { + const { run, stop } = Poller(); + + const startEnterpriseConnectionLinkFlow = async ({ + redirectUrl, + }: StartEnterpriseConnectionLinkFlowParams): Promise => { + if (!this.id) { + clerkVerifyEmailAddressCalledBeforeCreate('SignUp'); + } + const response = await this.prepareVerification({ + strategy: 'saml', + redirectUrl: redirectUrl, + }); + if (!response.verification.externalVerificationRedirectURL) { + throw Error('Unexpected: External verification redirect URL is missing'); + } + window.open(response.verification.externalVerificationRedirectURL, '_blank'); + return new Promise((resolve, reject) => { + void run(() => { + return this.reload() + .then(res => { + if (res.verification.status === 'verified') { + stop(); + resolve(res); + } + }) + .catch(err => { + stop(); + reject(err); + }); + }); + }); + }; + return { startEnterpriseConnectionLinkFlow, cancelEnterpriseConnectionLinkFlow: stop }; + }; + destroy = (): Promise => this._baseDelete(); toString = (): string => this.emailAddress; @@ -89,6 +131,7 @@ export class EmailAddress extends BaseResource implements EmailAddressResource { this.id = data.id; this.emailAddress = data.email_address; this.verification = new Verification(data.verification); + this.matchesEnterpriseConnection = data.matches_enterprise_connection; this.linkedTo = (data.linked_to || []).map(link => new IdentificationLink(link)); return this; } diff --git a/packages/clerk-js/src/ui/components/UserProfile/EmailForm.tsx b/packages/clerk-js/src/ui/components/UserProfile/EmailForm.tsx index 730b69ddd3..1c630e5c2d 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/EmailForm.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/EmailForm.tsx @@ -9,8 +9,9 @@ import type { FormProps } from '../../elements'; import { Form, FormButtons, FormContainer, useCardState, withCardStateProvider } from '../../elements'; import { useAssurance } from '../../hooks/useAssurance'; import { handleError, useFormControl } from '../../utils'; -import { emailLinksEnabledForInstance } from './utils'; +import { emailLinksEnabledForInstance, getVerificationStrategy } from './utils'; import { VerifyWithCode } from './VerifyWithCode'; +import { VerifyWithEnterpriseConnection } from './VerifyWithEnterpriseConnection'; import { VerifyWithLink } from './VerifyWithLink'; type EmailFormProps = FormProps & { @@ -23,8 +24,8 @@ export const EmailForm = withCardStateProvider((props: EmailFormProps) => { const { handleAssurance } = useAssurance(); const environment = useEnvironment(); const preferEmailLinks = emailLinksEnabledForInstance(environment); - const emailAddressRef = React.useRef(user?.emailAddresses.find(a => a.id === id)); + const strategy = getVerificationStrategy(emailAddressRef.current, preferEmailLinks); const wizard = useWizard({ defaultStep: emailAddressRef.current ? 1 : 0, onNextStep: () => card.setError(undefined), @@ -89,13 +90,14 @@ export const EmailForm = withCardStateProvider((props: EmailFormProps) => { }) } > - {preferEmailLinks ? ( + {strategy === 'email_link' && ( - ) : ( + )} + {strategy === 'email_code' && ( { onReset={onReset} /> )} + {strategy === 'saml' && ( + + )} ); diff --git a/packages/clerk-js/src/ui/components/UserProfile/VerifyWithEnterpriseConnection.tsx b/packages/clerk-js/src/ui/components/UserProfile/VerifyWithEnterpriseConnection.tsx new file mode 100644 index 0000000000..bb62c1f51b --- /dev/null +++ b/packages/clerk-js/src/ui/components/UserProfile/VerifyWithEnterpriseConnection.tsx @@ -0,0 +1,71 @@ +import type { EmailAddressResource } from '@clerk/types'; +import React from 'react'; + +import { EmailLinkStatusCard } from '../../common'; +import { buildEmailLinkRedirectUrl } from '../../common/redirects'; +import { useEnvironment, useUserProfileContext } from '../../contexts'; +import { Button, descriptors, localizationKeys } from '../../customizables'; +import { FormButtonContainer, useCardState, VerificationLink } from '../../elements'; +import { useEnterpriseConnectionLink } from '../../hooks'; +import { handleError } from '../../utils'; + +type VerifyWithEnterpriseConnectionProps = { + email: EmailAddressResource; + onReset: () => void; + nextStep: () => void; +}; + +export const VerifyWithEnterpriseConnection = (props: VerifyWithEnterpriseConnectionProps) => { + const { email, nextStep, onReset } = props; + const card = useCardState(); + const profileContext = useUserProfileContext(); + const { startEnterpriseConnectionLinkFlow } = useEnterpriseConnectionLink(email); + const { displayConfig } = useEnvironment(); + + React.useEffect(() => { + startVerification(); + }, []); + + function startVerification() { + /** + * The following workaround is used in order to make magic links work when the + * is used as a modal. In modals, the routing is virtual. For + * magic links the flow needs to end by invoking the /verify path of the + * that renders the . So, we use the userProfileUrl that + * defaults to Clerk Hosted Pages /user as a fallback. + */ + const { routing } = profileContext; + const baseUrl = routing === 'virtual' ? displayConfig.userProfileUrl : ''; + const redirectUrl = buildEmailLinkRedirectUrl(profileContext, baseUrl); + startEnterpriseConnectionLinkFlow({ redirectUrl }) + .then(() => nextStep()) + .catch(err => handleError(err, [], card.setError)); + } + + return ( + <> + + +