Skip to content

Commit

Permalink
feat(clerk-js,localizations,types): Enable email verification in `Use…
Browse files Browse the repository at this point in the history
…rProfile` via enterprise SSO (#4406)

Co-authored-by: Laura Beatris <[email protected]>
  • Loading branch information
NicolasLopes7 and LauraBeatris authored Jan 23, 2025
1 parent 5b4c220 commit 2179690
Show file tree
Hide file tree
Showing 55 changed files with 442 additions and 64 deletions.
5 changes: 5 additions & 0 deletions .changeset/eighty-jobs-impress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/types': minor
---

Deprecated `userProfile.emailAddressPage.emailLink.formHint` and `userProfile.emailAddressPage.emailCode.formHint` in favor of `userProfile.emailAddressPage.formHint`
5 changes: 5 additions & 0 deletions .changeset/funny-camels-chew.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/localizations': minor
---

Unified `formHint` under `userProfile.emailAddressPage` for all first factor auth methods
5 changes: 5 additions & 0 deletions .changeset/ninety-colts-bathe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/clerk-js': minor
---

Enable email verification in `UserProfile` via enterprise SSO
44 changes: 40 additions & 4 deletions packages/clerk-js/src/core/resources/EmailAddress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,23 @@ import { Poller } from '@clerk/shared/poller';
import type {
AttemptEmailAddressVerificationParams,
CreateEmailLinkFlowReturn,
CreateEnterpriseSSOLinkFlowReturn,
EmailAddressJSON,
EmailAddressJSONSnapshot,
EmailAddressResource,
IdentificationLinkResource,
PrepareEmailAddressVerificationParams,
StartEmailLinkFlowParams,
StartEnterpriseSSOLinkFlowParams,
VerificationResource,
} from '@clerk/types';

import { clerkVerifyEmailAddressCalledBeforeCreate } from '../errors';
import { BaseResource, IdentificationLink, Verification } from './internal';

export class EmailAddress extends BaseResource implements EmailAddressResource {
id!: string;
emailAddress = '';
matchesSsoConnection = false;
linkedTo: IdentificationLinkResource[] = [];
verification!: VerificationResource;

Expand Down Expand Up @@ -52,9 +54,6 @@ export class EmailAddress extends BaseResource implements EmailAddressResource {
const { run, stop } = Poller();

const startEmailLinkFlow = async ({ redirectUrl }: StartEmailLinkFlowParams): Promise<EmailAddressResource> => {
if (!this.id) {
clerkVerifyEmailAddressCalledBeforeCreate('SignUp');
}
await this.prepareVerification({
strategy: 'email_link',
redirectUrl: redirectUrl,
Expand All @@ -78,6 +77,41 @@ export class EmailAddress extends BaseResource implements EmailAddressResource {
return { startEmailLinkFlow, cancelEmailLinkFlow: stop };
};

createEnterpriseSSOLinkFlow = (): CreateEnterpriseSSOLinkFlowReturn<
StartEnterpriseSSOLinkFlowParams,
EmailAddressResource
> => {
const { run, stop } = Poller();

const startEnterpriseSSOLinkFlow = async ({
redirectUrl,
}: StartEnterpriseSSOLinkFlowParams): Promise<EmailAddressResource> => {
const response = await this.prepareVerification({
strategy: 'enterprise_sso',
redirectUrl,
});
if (!response.verification.externalVerificationRedirectURL) {
throw Error('Unexpected: External verification redirect URL is missing');
}
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 { startEnterpriseSSOLinkFlow, cancelEnterpriseSSOLinkFlow: stop };
};

destroy = (): Promise<void> => this._baseDelete();

toString = (): string => this.emailAddress;
Expand All @@ -90,6 +124,7 @@ export class EmailAddress extends BaseResource implements EmailAddressResource {
this.id = data.id;
this.emailAddress = data.email_address;
this.verification = new Verification(data.verification);
this.matchesSsoConnection = data.matches_sso_connection;
this.linkedTo = (data.linked_to || []).map(link => new IdentificationLink(link));
return this;
}
Expand All @@ -101,6 +136,7 @@ export class EmailAddress extends BaseResource implements EmailAddressResource {
email_address: this.emailAddress,
verification: this.verification.__internal_toSnapshot(),
linked_to: this.linkedTo.map(link => link.__internal_toSnapshot()),
matches_sso_connection: this.matchesSsoConnection,
};
}
}
34 changes: 17 additions & 17 deletions packages/clerk-js/src/ui/common/__tests__/redirects.test.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
import { buildEmailLinkRedirectUrl, buildSSOCallbackURL } from '../redirects';
import { buildSSOCallbackURL, buildVerificationRedirectUrl, buildVerificationRedirectUrl } from '../redirects';

describe('buildEmailLinkRedirectUrl(routing, baseUrl)', () => {
describe('buildVerificationRedirectUrl(routing, baseUrl)', () => {
it('defaults to hash based routing strategy on empty routing', function () {
expect(
buildEmailLinkRedirectUrl({ ctx: { path: '', authQueryString: '' } as any, baseUrl: '', intent: 'sign-in' }),
buildVerificationRedirectUrl({ ctx: { path: '', authQueryString: '' } as any, baseUrl: '', intent: 'sign-in' }),
).toBe('http://localhost/#/verify');
});

it('returns the magic link redirect url for components using path based routing ', function () {
expect(
buildEmailLinkRedirectUrl({
buildVerificationRedirectUrl({
ctx: { routing: 'path', authQueryString: '' } as any,
baseUrl: '',
intent: 'sign-in',
}),
).toBe('http://localhost/verify');

expect(
buildEmailLinkRedirectUrl({
buildVerificationRedirectUrl({
ctx: { routing: 'path', path: '/sign-in', authQueryString: '' } as any,
baseUrl: '',
intent: 'sign-in',
}),
).toBe('http://localhost/sign-in/verify');

expect(
buildEmailLinkRedirectUrl({
buildVerificationRedirectUrl({
ctx: {
routing: 'path',
path: '',
Expand All @@ -37,7 +37,7 @@ describe('buildEmailLinkRedirectUrl(routing, baseUrl)', () => {
).toBe('http://localhost/verify?redirectUrl=https://clerk.com');

expect(
buildEmailLinkRedirectUrl({
buildVerificationRedirectUrl({
ctx: {
routing: 'path',
path: '/sign-in',
Expand All @@ -49,7 +49,7 @@ describe('buildEmailLinkRedirectUrl(routing, baseUrl)', () => {
).toBe('http://localhost/sign-in/verify?redirectUrl=https://clerk.com');

expect(
buildEmailLinkRedirectUrl({
buildVerificationRedirectUrl({
ctx: {
routing: 'path',
path: '/sign-in',
Expand All @@ -63,7 +63,7 @@ describe('buildEmailLinkRedirectUrl(routing, baseUrl)', () => {

it('returns the magic link redirect url for components using hash based routing ', function () {
expect(
buildEmailLinkRedirectUrl({
buildVerificationRedirectUrl({
ctx: {
routing: 'hash',
authQueryString: '',
Expand All @@ -74,7 +74,7 @@ describe('buildEmailLinkRedirectUrl(routing, baseUrl)', () => {
).toBe('http://localhost/#/verify');

expect(
buildEmailLinkRedirectUrl({
buildVerificationRedirectUrl({
ctx: {
routing: 'hash',
path: '/sign-in',
Expand All @@ -86,7 +86,7 @@ describe('buildEmailLinkRedirectUrl(routing, baseUrl)', () => {
).toBe('http://localhost/#/verify');

expect(
buildEmailLinkRedirectUrl({
buildVerificationRedirectUrl({
ctx: {
routing: 'hash',
path: '',
Expand All @@ -98,7 +98,7 @@ describe('buildEmailLinkRedirectUrl(routing, baseUrl)', () => {
).toBe('http://localhost/#/verify?redirectUrl=https://clerk.com');

expect(
buildEmailLinkRedirectUrl({
buildVerificationRedirectUrl({
ctx: {
routing: 'hash',
path: '/sign-in',
Expand All @@ -110,7 +110,7 @@ describe('buildEmailLinkRedirectUrl(routing, baseUrl)', () => {
).toBe('http://localhost/#/verify?redirectUrl=https://clerk.com');

expect(
buildEmailLinkRedirectUrl({
buildVerificationRedirectUrl({
ctx: {
routing: 'hash',
path: '/sign-in',
Expand All @@ -124,7 +124,7 @@ describe('buildEmailLinkRedirectUrl(routing, baseUrl)', () => {

it('returns the magic link redirect url for components using virtual routing ', function () {
expect(
buildEmailLinkRedirectUrl({
buildVerificationRedirectUrl({
ctx: {
routing: 'virtual',
authQueryString: 'redirectUrl=https://clerk.com',
Expand All @@ -135,7 +135,7 @@ describe('buildEmailLinkRedirectUrl(routing, baseUrl)', () => {
).toBe('https://accounts.clerk.com/sign-in#/verify?redirectUrl=https://clerk.com');

expect(
buildEmailLinkRedirectUrl({
buildVerificationRedirectUrl({
ctx: {
routing: 'virtual',
} as any,
Expand All @@ -147,7 +147,7 @@ describe('buildEmailLinkRedirectUrl(routing, baseUrl)', () => {

it('returns the magic link redirect url for components using the combined flow based on intent', function () {
expect(
buildEmailLinkRedirectUrl({
buildVerificationRedirectUrl({
ctx: {
routing: 'path',
path: '/sign-up',
Expand All @@ -159,7 +159,7 @@ describe('buildEmailLinkRedirectUrl(routing, baseUrl)', () => {
).toBe('http://localhost/sign-up/create/verify');

expect(
buildEmailLinkRedirectUrl({
buildVerificationRedirectUrl({
ctx: {
routing: 'path',
path: '/sign-in',
Expand Down
2 changes: 1 addition & 1 deletion packages/clerk-js/src/ui/common/redirects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { SignInContextType, SignUpContextType, UserProfileContextType } fro
export const SSO_CALLBACK_PATH_ROUTE = '/sso-callback';
export const MAGIC_LINK_VERIFY_PATH_ROUTE = '/verify';

export function buildEmailLinkRedirectUrl({
export function buildVerificationRedirectUrl({
ctx,
baseUrl = '',
intent = 'sign-in',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { EmailLinkFactor, SignInResource } from '@clerk/types';
import React from 'react';

import { EmailLinkStatusCard } from '../../common';
import { buildEmailLinkRedirectUrl } from '../../common/redirects';
import { buildVerificationRedirectUrl } from '../../common/redirects';
import { useCoreSignIn, useSignInContext } from '../../contexts';
import { Flow, localizationKeys, useLocalizations } from '../../customizables';
import type { VerificationCodeCardProps } from '../../elements';
Expand Down Expand Up @@ -45,7 +45,7 @@ export const SignInFactorOneEmailLinkCard = (props: SignInFactorOneEmailLinkCard
const startEmailLinkVerification = () => {
startEmailLinkFlow({
emailAddressId: props.factor.emailAddressId,
redirectUrl: buildEmailLinkRedirectUrl({ ctx: signInContext, baseUrl: signInUrl, intent: 'sign-in' }),
redirectUrl: buildVerificationRedirectUrl({ ctx: signInContext, baseUrl: signInUrl, intent: 'sign-in' }),
})
.then(res => handleVerificationResult(res))
.catch(err => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ const ConnectedAccount = ({ account }: { account: ExternalAccountResource }) =>
<ImageOrInitial />
<Box sx={{ whiteSpace: 'nowrap', overflow: 'hidden' }}>
<Flex
gap={2}
gap={1}
center
>
<Text sx={t => ({ color: t.colors.$colorText })}>{`${
Expand Down
Loading

0 comments on commit 2179690

Please sign in to comment.