Skip to content

Commit 2a03cf1

Browse files
NicolasLopes7LauraBeatris
authored andcommitted
feat(clerk-js,localizations,types): Enable email verification in UserProfile via enterprise SSO (#4406)
Co-authored-by: Laura Beatris <[email protected]>
1 parent 154c911 commit 2a03cf1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+442
-64
lines changed

.changeset/eighty-jobs-impress.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clerk/types': minor
3+
---
4+
5+
Deprecated `userProfile.emailAddressPage.emailLink.formHint` and `userProfile.emailAddressPage.emailCode.formHint` in favor of `userProfile.emailAddressPage.formHint`

.changeset/funny-camels-chew.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clerk/localizations': minor
3+
---
4+
5+
Unified `formHint` under `userProfile.emailAddressPage` for all first factor auth methods

.changeset/ninety-colts-bathe.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clerk/clerk-js': minor
3+
---
4+
5+
Enable email verification in `UserProfile` via enterprise SSO

packages/clerk-js/src/core/resources/EmailAddress.ts

+40-4
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,23 @@ import { Poller } from '@clerk/shared/poller';
22
import type {
33
AttemptEmailAddressVerificationParams,
44
CreateEmailLinkFlowReturn,
5+
CreateEnterpriseSSOLinkFlowReturn,
56
EmailAddressJSON,
67
EmailAddressJSONSnapshot,
78
EmailAddressResource,
89
IdentificationLinkResource,
910
PrepareEmailAddressVerificationParams,
1011
StartEmailLinkFlowParams,
12+
StartEnterpriseSSOLinkFlowParams,
1113
VerificationResource,
1214
} from '@clerk/types';
1315

14-
import { clerkVerifyEmailAddressCalledBeforeCreate } from '../errors';
1516
import { BaseResource, IdentificationLink, Verification } from './internal';
1617

1718
export class EmailAddress extends BaseResource implements EmailAddressResource {
1819
id!: string;
1920
emailAddress = '';
21+
matchesSsoConnection = false;
2022
linkedTo: IdentificationLinkResource[] = [];
2123
verification!: VerificationResource;
2224

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

5456
const startEmailLinkFlow = async ({ redirectUrl }: StartEmailLinkFlowParams): Promise<EmailAddressResource> => {
55-
if (!this.id) {
56-
clerkVerifyEmailAddressCalledBeforeCreate('SignUp');
57-
}
5857
await this.prepareVerification({
5958
strategy: 'email_link',
6059
redirectUrl: redirectUrl,
@@ -78,6 +77,41 @@ export class EmailAddress extends BaseResource implements EmailAddressResource {
7877
return { startEmailLinkFlow, cancelEmailLinkFlow: stop };
7978
};
8079

80+
createEnterpriseSSOLinkFlow = (): CreateEnterpriseSSOLinkFlowReturn<
81+
StartEnterpriseSSOLinkFlowParams,
82+
EmailAddressResource
83+
> => {
84+
const { run, stop } = Poller();
85+
86+
const startEnterpriseSSOLinkFlow = async ({
87+
redirectUrl,
88+
}: StartEnterpriseSSOLinkFlowParams): Promise<EmailAddressResource> => {
89+
const response = await this.prepareVerification({
90+
strategy: 'enterprise_sso',
91+
redirectUrl,
92+
});
93+
if (!response.verification.externalVerificationRedirectURL) {
94+
throw Error('Unexpected: External verification redirect URL is missing');
95+
}
96+
return new Promise((resolve, reject) => {
97+
void run(() => {
98+
return this.reload()
99+
.then(res => {
100+
if (res.verification.status === 'verified') {
101+
stop();
102+
resolve(res);
103+
}
104+
})
105+
.catch(err => {
106+
stop();
107+
reject(err);
108+
});
109+
});
110+
});
111+
};
112+
return { startEnterpriseSSOLinkFlow, cancelEnterpriseSSOLinkFlow: stop };
113+
};
114+
81115
destroy = (): Promise<void> => this._baseDelete();
82116

83117
toString = (): string => this.emailAddress;
@@ -90,6 +124,7 @@ export class EmailAddress extends BaseResource implements EmailAddressResource {
90124
this.id = data.id;
91125
this.emailAddress = data.email_address;
92126
this.verification = new Verification(data.verification);
127+
this.matchesSsoConnection = data.matches_sso_connection;
93128
this.linkedTo = (data.linked_to || []).map(link => new IdentificationLink(link));
94129
return this;
95130
}
@@ -101,6 +136,7 @@ export class EmailAddress extends BaseResource implements EmailAddressResource {
101136
email_address: this.emailAddress,
102137
verification: this.verification.__internal_toSnapshot(),
103138
linked_to: this.linkedTo.map(link => link.__internal_toSnapshot()),
139+
matches_sso_connection: this.matchesSsoConnection,
104140
};
105141
}
106142
}

packages/clerk-js/src/ui/common/__tests__/redirects.test.ts

+17-17
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,31 @@
1-
import { buildEmailLinkRedirectUrl, buildSSOCallbackURL } from '../redirects';
1+
import { buildSSOCallbackURL, buildVerificationRedirectUrl, buildVerificationRedirectUrl } from '../redirects';
22

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

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

1919
expect(
20-
buildEmailLinkRedirectUrl({
20+
buildVerificationRedirectUrl({
2121
ctx: { routing: 'path', path: '/sign-in', authQueryString: '' } as any,
2222
baseUrl: '',
2323
intent: 'sign-in',
2424
}),
2525
).toBe('http://localhost/sign-in/verify');
2626

2727
expect(
28-
buildEmailLinkRedirectUrl({
28+
buildVerificationRedirectUrl({
2929
ctx: {
3030
routing: 'path',
3131
path: '',
@@ -37,7 +37,7 @@ describe('buildEmailLinkRedirectUrl(routing, baseUrl)', () => {
3737
).toBe('http://localhost/verify?redirectUrl=https://clerk.com');
3838

3939
expect(
40-
buildEmailLinkRedirectUrl({
40+
buildVerificationRedirectUrl({
4141
ctx: {
4242
routing: 'path',
4343
path: '/sign-in',
@@ -49,7 +49,7 @@ describe('buildEmailLinkRedirectUrl(routing, baseUrl)', () => {
4949
).toBe('http://localhost/sign-in/verify?redirectUrl=https://clerk.com');
5050

5151
expect(
52-
buildEmailLinkRedirectUrl({
52+
buildVerificationRedirectUrl({
5353
ctx: {
5454
routing: 'path',
5555
path: '/sign-in',
@@ -63,7 +63,7 @@ describe('buildEmailLinkRedirectUrl(routing, baseUrl)', () => {
6363

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

7676
expect(
77-
buildEmailLinkRedirectUrl({
77+
buildVerificationRedirectUrl({
7878
ctx: {
7979
routing: 'hash',
8080
path: '/sign-in',
@@ -86,7 +86,7 @@ describe('buildEmailLinkRedirectUrl(routing, baseUrl)', () => {
8686
).toBe('http://localhost/#/verify');
8787

8888
expect(
89-
buildEmailLinkRedirectUrl({
89+
buildVerificationRedirectUrl({
9090
ctx: {
9191
routing: 'hash',
9292
path: '',
@@ -98,7 +98,7 @@ describe('buildEmailLinkRedirectUrl(routing, baseUrl)', () => {
9898
).toBe('http://localhost/#/verify?redirectUrl=https://clerk.com');
9999

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

112112
expect(
113-
buildEmailLinkRedirectUrl({
113+
buildVerificationRedirectUrl({
114114
ctx: {
115115
routing: 'hash',
116116
path: '/sign-in',
@@ -124,7 +124,7 @@ describe('buildEmailLinkRedirectUrl(routing, baseUrl)', () => {
124124

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

137137
expect(
138-
buildEmailLinkRedirectUrl({
138+
buildVerificationRedirectUrl({
139139
ctx: {
140140
routing: 'virtual',
141141
} as any,
@@ -147,7 +147,7 @@ describe('buildEmailLinkRedirectUrl(routing, baseUrl)', () => {
147147

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

161161
expect(
162-
buildEmailLinkRedirectUrl({
162+
buildVerificationRedirectUrl({
163163
ctx: {
164164
routing: 'path',
165165
path: '/sign-in',

packages/clerk-js/src/ui/common/redirects.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { SignInContextType, SignUpContextType, UserProfileContextType } fro
44
export const SSO_CALLBACK_PATH_ROUTE = '/sso-callback';
55
export const MAGIC_LINK_VERIFY_PATH_ROUTE = '/verify';
66

7-
export function buildEmailLinkRedirectUrl({
7+
export function buildVerificationRedirectUrl({
88
ctx,
99
baseUrl = '',
1010
intent = 'sign-in',

packages/clerk-js/src/ui/components/SignIn/SignInFactorOneEmailLinkCard.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { EmailLinkFactor, SignInResource } from '@clerk/types';
44
import React from 'react';
55

66
import { EmailLinkStatusCard } from '../../common';
7-
import { buildEmailLinkRedirectUrl } from '../../common/redirects';
7+
import { buildVerificationRedirectUrl } from '../../common/redirects';
88
import { useCoreSignIn, useSignInContext } from '../../contexts';
99
import { Flow, localizationKeys, useLocalizations } from '../../customizables';
1010
import type { VerificationCodeCardProps } from '../../elements';
@@ -45,7 +45,7 @@ export const SignInFactorOneEmailLinkCard = (props: SignInFactorOneEmailLinkCard
4545
const startEmailLinkVerification = () => {
4646
startEmailLinkFlow({
4747
emailAddressId: props.factor.emailAddressId,
48-
redirectUrl: buildEmailLinkRedirectUrl({ ctx: signInContext, baseUrl: signInUrl, intent: 'sign-in' }),
48+
redirectUrl: buildVerificationRedirectUrl({ ctx: signInContext, baseUrl: signInUrl, intent: 'sign-in' }),
4949
})
5050
.then(res => handleVerificationResult(res))
5151
.catch(err => {

packages/clerk-js/src/ui/components/UserProfile/ConnectedAccountsSection.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ const ConnectedAccount = ({ account }: { account: ExternalAccountResource }) =>
164164
<ImageOrInitial />
165165
<Box sx={{ whiteSpace: 'nowrap', overflow: 'hidden' }}>
166166
<Flex
167-
gap={2}
167+
gap={1}
168168
center
169169
>
170170
<Text sx={t => ({ color: t.colors.$colorText })}>{`${

0 commit comments

Comments
 (0)