Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
NicolasLopes7 committed Oct 24, 2024
1 parent 0d923c1 commit c701b85
Show file tree
Hide file tree
Showing 11 changed files with 206 additions and 10 deletions.
10 changes: 6 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 43 additions & 0 deletions packages/clerk-js/src/core/resources/EmailAddress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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;

Expand Down Expand Up @@ -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<EmailAddressResource> => {
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<void> => this._baseDelete();

toString = (): string => this.emailAddress;
Expand All @@ -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;
}
Expand Down
17 changes: 13 additions & 4 deletions packages/clerk-js/src/ui/components/UserProfile/EmailForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 & {
Expand All @@ -23,8 +24,8 @@ export const EmailForm = withCardStateProvider((props: EmailFormProps) => {
const { handleAssurance } = useAssurance();
const environment = useEnvironment();
const preferEmailLinks = emailLinksEnabledForInstance(environment);

const emailAddressRef = React.useRef<EmailAddressResource | undefined>(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),
Expand Down Expand Up @@ -89,13 +90,14 @@ export const EmailForm = withCardStateProvider((props: EmailFormProps) => {
})
}
>
{preferEmailLinks ? (
{strategy === 'email_link' && (
<VerifyWithLink
nextStep={onSuccess}
email={emailAddressRef.current as any}
onReset={onReset}
/>
) : (
)}
{strategy === 'email_code' && (
<VerifyWithCode
nextStep={onSuccess}
identification={emailAddressRef.current}
Expand All @@ -104,6 +106,13 @@ export const EmailForm = withCardStateProvider((props: EmailFormProps) => {
onReset={onReset}
/>
)}
{strategy === 'saml' && (
<VerifyWithEnterpriseConnection
nextStep={onSuccess}
email={emailAddressRef.current as any}
onReset={onReset}
/>
)}
</FormContainer>
</Wizard>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -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
* <UserProfile/> 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 <UserProfile/>
* that renders the <VerificationSuccessPage/>. 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 (
<>
<VerificationLink
resendButton={localizationKeys('userProfile.emailAddressPage.emailLink.resendButton')}
onResendCodeClicked={startVerification}
/>
<FormButtonContainer>
<Button
variant='ghost'
localizationKey={localizationKeys('userProfile.formButtonReset')}
elementDescriptor={descriptors.formButtonReset}
onClick={onReset}
/>
</FormButtonContainer>
</>
);
};

export const VerificationSuccessPage = () => {
return (
<EmailLinkStatusCard
title={localizationKeys('signUp.emailLink.verifiedSwitchTab.title')}
subtitle={localizationKeys('signUp.emailLink.verifiedSwitchTab.subtitle')}
status='verified'
/>
);
};
12 changes: 12 additions & 0 deletions packages/clerk-js/src/ui/components/UserProfile/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type {
EmailAddressResource,
EnvironmentResource,
PhoneNumberResource,
PrepareEmailAddressVerificationParams,
UserResource,
} from '@clerk/types';

Expand Down Expand Up @@ -76,3 +77,14 @@ export function sortIdentificationBasedOnVerification<T extends Array<EmailAddre

return [...primaryItem, ...verifiedItems, ...unverifiedItems, ...unverifiedItemsWithoutVerification] as T;
}

export const getVerificationStrategy = (
emailAddress: EmailAddressResource | undefined,
preferLinks: boolean,
): PrepareEmailAddressVerificationParams['strategy'] => {
if (emailAddress?.matchesEnterpriseConnection) {
return 'saml';
}

return preferLinks ? 'email_link' : 'email_code';
};
1 change: 1 addition & 0 deletions packages/clerk-js/src/ui/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ export * from './useDebounce';
export * from './useScrollLock';
export * from './useClerkModalStateParams';
export * from './useNavigateToFlowStart';
export * from './useEnterpriseConnectionLink';
32 changes: 32 additions & 0 deletions packages/clerk-js/src/ui/hooks/useEnterpriseConnectionLink.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type {
CreateEnterpriseConnectionLinkFlowReturn,
EmailAddressResource,
StartEnterpriseConnectionLinkFlowParams,
} from '@clerk/types';
import React from 'react';

type EnterpriseConnectionLinkable = EmailAddressResource;
type EnterpriseConnectionLinkEmailAddressReturn = CreateEnterpriseConnectionLinkFlowReturn<
StartEnterpriseConnectionLinkFlowParams,
EmailAddressResource
>;

function useEnterpriseConnectionLink(
resource: EnterpriseConnectionLinkable,
): EnterpriseConnectionLinkEmailAddressReturn {
const { startEnterpriseConnectionLinkFlow, cancelEnterpriseConnectionLinkFlow } = React.useMemo(
() => resource.createEnterpriseConnectionLinkFlow(),
[resource],
);

React.useEffect(() => {
return cancelEnterpriseConnectionLinkFlow;
}, []);

return {
startEnterpriseConnectionLinkFlow,
cancelEnterpriseConnectionLinkFlow,
};
}

export { useEnterpriseConnectionLink };
19 changes: 17 additions & 2 deletions packages/types/src/emailAddress.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import type { IdentificationLinkResource } from './identificationLink';
import type { ClerkResource } from './resource';
import type { EmailCodeStrategy, EmailLinkStrategy } from './strategies';
import type { CreateEmailLinkFlowReturn, StartEmailLinkFlowParams, VerificationResource } from './verification';
import type { EmailCodeStrategy, EmailLinkStrategy, EmailSAMLStrategy } from './strategies';
import type {
CreateEmailLinkFlowReturn,
CreateEnterpriseConnectionLinkFlowReturn,
StartEmailLinkFlowParams,
StartEnterpriseConnectionLinkFlowParams,
VerificationResource,
} from './verification';

export type PrepareEmailAddressVerificationParams =
| {
Expand All @@ -10,6 +16,10 @@ export type PrepareEmailAddressVerificationParams =
| {
strategy: EmailLinkStrategy;
redirectUrl: string;
}
| {
strategy: EmailSAMLStrategy;
redirectUrl: string;
};

export type AttemptEmailAddressVerificationParams = {
Expand All @@ -20,11 +30,16 @@ export interface EmailAddressResource extends ClerkResource {
id: string;
emailAddress: string;
verification: VerificationResource;
matchesEnterpriseConnection: boolean;
linkedTo: IdentificationLinkResource[];
toString: () => string;
prepareVerification: (params: PrepareEmailAddressVerificationParams) => Promise<EmailAddressResource>;
attemptVerification: (params: AttemptEmailAddressVerificationParams) => Promise<EmailAddressResource>;
createEmailLinkFlow: () => CreateEmailLinkFlowReturn<StartEmailLinkFlowParams, EmailAddressResource>;
createEnterpriseConnectionLinkFlow: () => CreateEnterpriseConnectionLinkFlowReturn<
StartEnterpriseConnectionLinkFlowParams,
EmailAddressResource
>;
destroy: () => Promise<void>;
create: () => Promise<EmailAddressResource>;
}
1 change: 1 addition & 0 deletions packages/types/src/json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ export interface EmailAddressJSON extends ClerkResourceJSON {
email_address: string;
verification: VerificationJSON | null;
linked_to: IdentificationLinkJSON[];
matches_enterprise_connection: boolean;
}

export interface IdentificationLinkJSON extends ClerkResourceJSON {
Expand Down
1 change: 1 addition & 0 deletions packages/types/src/strategies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export type PasswordStrategy = 'password';
export type PhoneCodeStrategy = 'phone_code';
export type EmailCodeStrategy = 'email_code';
export type EmailLinkStrategy = 'email_link';
export type EmailSAMLStrategy = 'saml';
export type TicketStrategy = 'ticket';
export type TOTPStrategy = 'totp';
export type BackupCodeStrategy = 'backup_code';
Expand Down
9 changes: 9 additions & 0 deletions packages/types/src/verification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,12 @@ export type CreateEmailLinkFlowReturn<Params, Resource> = {
startEmailLinkFlow: (params: Params) => Promise<Resource>;
cancelEmailLinkFlow: () => void;
};

export interface StartEnterpriseConnectionLinkFlowParams {
redirectUrl: string;
}

export type CreateEnterpriseConnectionLinkFlowReturn<Params, Resource> = {
startEnterpriseConnectionLinkFlow: (params: Params) => Promise<Resource>;
cancelEnterpriseConnectionLinkFlow: () => void;
};

0 comments on commit c701b85

Please sign in to comment.