Skip to content

Commit b7a4e1e

Browse files
authored
fix(clerk-js,shared): Handle unsafeMetadata in transfer flows (#7647)
1 parent ddf519b commit b7a4e1e

File tree

9 files changed

+140
-2
lines changed

9 files changed

+140
-2
lines changed

.changeset/smooth-planes-work.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@clerk/clerk-js': patch
3+
'@clerk/shared': patch
4+
---
5+
6+
Fix `unsafeMetadata` being lost when users are transferred between sign-in and sign-up flows during OAuth/SSO authentication

packages/clerk-js/src/core/__tests__/clerk.test.ts

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1094,7 +1094,67 @@ describe('Clerk singleton', () => {
10941094
await sut.handleRedirectCallback();
10951095

10961096
await waitFor(() => {
1097-
expect(mockSignUpCreate).toHaveBeenCalledWith({ transfer: true });
1097+
expect(mockSignUpCreate).toHaveBeenCalledWith({ transfer: true, unsafeMetadata: undefined });
1098+
expect(mockSetActive).toHaveBeenCalled();
1099+
});
1100+
});
1101+
1102+
it('passes unsafeMetadata to signUp.create during OAuth transfer flow', async () => {
1103+
mockEnvironmentFetch.mockReturnValue(
1104+
Promise.resolve({
1105+
authConfig: {},
1106+
userSettings: mockUserSettings,
1107+
displayConfig: mockDisplayConfig,
1108+
isSingleSession: () => false,
1109+
isProduction: () => false,
1110+
isDevelopmentOrStaging: () => true,
1111+
onWindowLocationHost: () => false,
1112+
}),
1113+
);
1114+
1115+
mockClientFetch.mockReturnValue(
1116+
Promise.resolve({
1117+
signedInSessions: [],
1118+
signIn: new SignIn({
1119+
status: 'needs_identifier',
1120+
first_factor_verification: {
1121+
status: 'transferable',
1122+
strategy: 'oauth_google',
1123+
external_verification_redirect_url: '',
1124+
error: {
1125+
code: 'external_account_not_found',
1126+
long_message: 'The External Account was not found.',
1127+
message: 'Invalid external account',
1128+
},
1129+
},
1130+
second_factor_verification: null,
1131+
identifier: '',
1132+
user_data: null,
1133+
created_session_id: null,
1134+
created_user_id: null,
1135+
} as any as SignInJSON),
1136+
signUp: new SignUp(null),
1137+
}),
1138+
);
1139+
1140+
const mockSetActive = vi.fn();
1141+
const mockSignUpCreate = vi
1142+
.fn()
1143+
.mockReturnValue(Promise.resolve({ status: 'complete', createdSessionId: '123' }));
1144+
1145+
const sut = new Clerk(productionPublishableKey);
1146+
await sut.load(mockedLoadOptions);
1147+
if (!sut.client) {
1148+
fail('we should always have a client');
1149+
}
1150+
sut.client.signUp.create = mockSignUpCreate;
1151+
sut.setActive = mockSetActive;
1152+
1153+
const unsafeMetadata = { foo: 'bar', nested: { value: 123 } };
1154+
await sut.handleRedirectCallback({ unsafeMetadata });
1155+
1156+
await waitFor(() => {
1157+
expect(mockSignUpCreate).toHaveBeenCalledWith({ transfer: true, unsafeMetadata });
10981158
expect(mockSetActive).toHaveBeenCalled();
10991159
});
11001160
});

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2207,7 +2207,7 @@ export class Clerk implements ClerkInterface {
22072207
return navigateToSignIn();
22082208
}
22092209

2210-
const res = await signUp.create({ transfer: true });
2210+
const res = await signUp.create({ transfer: true, unsafeMetadata: params.unsafeMetadata });
22112211
switch (res.status) {
22122212
case 'complete':
22132213
return this.setActive({

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,7 @@ function SignInStartInternal(): JSX.Element {
504504
attribute,
505505
identifierField.value,
506506
),
507+
unsafeMetadata: ctx.unsafeMetadata,
507508
});
508509
} else {
509510
handleError(e, [identifierField, instantPasswordField], card.setError);

packages/clerk-js/src/ui/components/SignIn/__tests__/handleCombinedFlowTransfer.test.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,67 @@ describe('handleCombinedFlowTransfer', () => {
4747
expect(mockCompleteSignUpFlow).toHaveBeenCalled();
4848
});
4949

50+
it('should pass unsafeMetadata to signUp.create', async () => {
51+
const mockCreate = vi.fn().mockResolvedValue({});
52+
const mockClerk = {
53+
client: {
54+
signUp: {
55+
create: mockCreate,
56+
optionalFields: [],
57+
},
58+
},
59+
};
60+
61+
const unsafeMetadata = { foo: 'bar', nested: { value: 123 } };
62+
63+
await handleCombinedFlowTransfer({
64+
identifierAttribute: 'emailAddress',
65+
identifierValue: 'test@test.com',
66+
signUpMode: 'public',
67+
navigate: mockNavigate,
68+
handleError: mockHandleError,
69+
clerk: mockClerk as unknown as LoadedClerk,
70+
afterSignUpUrl: 'https://test.com',
71+
passwordEnabled: false,
72+
navigateOnSetActive: vi.fn(),
73+
unsafeMetadata,
74+
});
75+
76+
expect(mockCreate).toHaveBeenCalledWith({
77+
emailAddress: 'test@test.com',
78+
unsafeMetadata,
79+
});
80+
});
81+
82+
it('should pass undefined unsafeMetadata when not provided', async () => {
83+
const mockCreate = vi.fn().mockResolvedValue({});
84+
const mockClerk = {
85+
client: {
86+
signUp: {
87+
create: mockCreate,
88+
optionalFields: [],
89+
},
90+
},
91+
};
92+
93+
await handleCombinedFlowTransfer({
94+
identifierAttribute: 'emailAddress',
95+
identifierValue: 'test@test.com',
96+
signUpMode: 'public',
97+
navigate: mockNavigate,
98+
handleError: mockHandleError,
99+
clerk: mockClerk as unknown as LoadedClerk,
100+
afterSignUpUrl: 'https://test.com',
101+
passwordEnabled: false,
102+
navigateOnSetActive: vi.fn(),
103+
});
104+
105+
expect(mockCreate).toHaveBeenCalledWith({
106+
emailAddress: 'test@test.com',
107+
unsafeMetadata: undefined,
108+
});
109+
});
110+
50111
it('should call completeSignUpFlow with phone number if phone number is optional field.', async () => {
51112
const mockClerk = {
52113
client: {

packages/clerk-js/src/ui/components/SignIn/handleCombinedFlowTransfer.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ type HandleCombinedFlowTransferProps = {
2525
passwordEnabled: boolean;
2626
alternativePhoneCodeChannel?: PhoneCodeChannel | null;
2727
navigateOnSetActive: (opts: { session: SessionResource; redirectUrl: string }) => Promise<unknown>;
28+
unsafeMetadata?: SignUpUnsafeMetadata;
2829
};
2930

3031
/**
@@ -45,6 +46,7 @@ export function handleCombinedFlowTransfer({
4546
passwordEnabled,
4647
navigateOnSetActive,
4748
alternativePhoneCodeChannel,
49+
unsafeMetadata,
4850
}: HandleCombinedFlowTransferProps): Promise<unknown> | void {
4951
if (signUpMode === SIGN_UP_MODES.WAITLIST) {
5052
const waitlistUrl = clerk.buildWaitlistUrl(
@@ -85,6 +87,7 @@ export function handleCombinedFlowTransfer({
8587
.create({
8688
[identifierAttribute]: identifierValue,
8789
...alternativePhoneCodeChannelParams,
90+
unsafeMetadata,
8891
})
8992
.then(async res => {
9093
const completeSignUpFlow = await lazyCompleteSignUpFlow();

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ function SignInRoutes(): JSX.Element {
7373
firstFactorUrl={'../factor-one'}
7474
secondFactorUrl={'../factor-two'}
7575
resetPasswordUrl={'../reset-password'}
76+
unsafeMetadata={signInContext.unsafeMetadata}
7677
/>
7778
</Route>
7879
<Route path='choose'>
@@ -112,6 +113,7 @@ function SignInRoutes(): JSX.Element {
112113
continueSignUpUrl='../continue'
113114
verifyEmailAddressUrl='../verify-email-address'
114115
verifyPhoneNumberUrl='../verify-phone-number'
116+
unsafeMetadata={signUpContext.unsafeMetadata}
115117
/>
116118
</Route>
117119
<Route path='verify'>

packages/clerk-js/src/ui/components/SignUp/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ function SignUpRoutes(): JSX.Element {
5555
continueSignUpUrl='../continue'
5656
verifyEmailAddressUrl='../verify-email-address'
5757
verifyPhoneNumberUrl='../verify-phone-number'
58+
unsafeMetadata={signUpContext.unsafeMetadata}
5859
/>
5960
</Route>
6061
<Route path='verify'>

packages/shared/src/types/clerk.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1022,6 +1022,10 @@ export type HandleOAuthCallbackParams = TransferableOption &
10221022
* The underlying resource to optionally reload before processing an OAuth callback.
10231023
*/
10241024
reloadResource?: 'signIn' | 'signUp';
1025+
/**
1026+
* Additional arbitrary metadata to be stored alongside the User object when a sign-up transfer occurs.
1027+
*/
1028+
unsafeMetadata?: SignUpUnsafeMetadata;
10251029
};
10261030

10271031
export type HandleSamlCallbackParams = HandleOAuthCallbackParams;

0 commit comments

Comments
 (0)