Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/small-llamas-love.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/clerk-js': minor
---

Fix issue where `unsafeMetadata` was not associated with sign-ups in the combined sign-in-or-up flow.
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ export default function Page() {
<div>
<SignIn
routing={'path'}
path={'/sign-in'}
path={'/sign-in-or-up'}
signUpUrl={'/sign-up'}
fallback={<>Loading sign in</>}
unsafeMetadata={{ position: 'goalie' }}
withSignUp
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export default function Page() {
path={'/sign-up'}
signInUrl={'/sign-in'}
fallback={<>Loading sign up</>}
unsafeMetadata={{ position: 'goalie' }}
/>
</div>
);
Expand Down
24 changes: 24 additions & 0 deletions integration/testUtils/usersService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export type UserService = {
createBapiUser: (fakeUser: FakeUser) => Promise<User>;
deleteIfExists: (opts: { id?: string; email?: string }) => Promise<void>;
createFakeOrganization: (userId: string) => Promise<FakeOrganization>;
getUser: (opts: { id?: string; email?: string }) => Promise<User | undefined>;
};

/**
Expand Down Expand Up @@ -116,6 +117,29 @@ export const createUserService = (clerkClient: ClerkClient) => {

await clerkClient.users.deleteUser(id);
},
getUser: async (opts: { id?: string; email?: string }) => {
if (opts.id) {
try {
const user = await clerkClient.users.getUser(opts.id);
return user;
} catch (err) {
console.log(`Error fetching user "${opts.id}": ${err.message}`);
return;
}
}

if (opts.email) {
const { data: users } = await clerkClient.users.getUserList({ emailAddress: [opts.email] });
if (users.length > 0) {
return users[0];
} else {
console.log(`User "${opts.email}" does not exist!`);
return;
}
}

throw new Error('Either id or email must be provided');
},
createFakeOrganization: async userId => {
const name = faker.animal.dog();
const organization = await clerkClient.organizations.createOrganization({
Expand Down
2 changes: 1 addition & 1 deletion integration/tests/sign-in-or-up-component.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ test.describe('sign-in-or-up component initialization flow @nextjs', () => {
test('flows are combined', async ({ page, context }) => {
const u = createTestUtils({ app, page, context });
await u.page.goToRelative('/sign-in-or-up');
await expect(u.page.getByText(`Don’t have an account?`)).toBeHidden();
await expect(u.page.getByText(`Continue to`)).toBeVisible();
});
});
71 changes: 71 additions & 0 deletions integration/tests/unsafeMetadata.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { expect, test } from '@playwright/test';

import { appConfigs } from '../presets';
import { createTestUtils, testAgainstRunningApps } from '../testUtils';

testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })('unsafeMetadata @nextjs', ({ app }) => {
test.describe.configure({ mode: 'parallel' });

test.afterAll(async () => {
await app.teardown();
});

test('sign up persists unsafeMetadata', async ({ page, context }) => {
const u = createTestUtils({ app, page, context });
const fakeUser = u.services.users.createFakeUser({
fictionalEmail: true,
withPhoneNumber: true,
withUsername: true,
});

// Go to sign up page
await u.po.signUp.goTo();

// Fill in sign up form
await u.po.signUp.signUpWithEmailAndPassword({
email: fakeUser.email,
password: fakeUser.password,
});

// Verify email
await u.po.signUp.enterTestOtpCode();

// Check if user is signed in
await u.po.expect.toBeSignedIn();

const user = await u.services.users.getUser({ email: fakeUser.email });
expect(user?.unsafeMetadata).toEqual({ position: 'goalie' });

await fakeUser.deleteIfExists();
});

test('combined sign up persists unsafeMetadata', async ({ page, context }) => {
const u = createTestUtils({ app, page, context });
const fakeUser = u.services.users.createFakeUser({
fictionalEmail: true,
withPassword: true,
withUsername: true,
});

await u.page.goToRelative('/sign-in-or-up');
await u.po.signIn.setIdentifier(fakeUser.username);
await u.po.signIn.continue();
await u.page.waitForAppUrl('/sign-in-or-up/create');

const prefilledUsername = u.po.signUp.getUsernameInput();
await expect(prefilledUsername).toHaveValue(fakeUser.username);

await u.po.signUp.setEmailAddress(fakeUser.email);
await u.po.signUp.setPassword(fakeUser.password);
await u.po.signUp.continue();

await u.po.signUp.enterTestOtpCode();

await u.po.expect.toBeSignedIn();

const user = await u.services.users.getUser({ email: fakeUser.email });
expect(user?.unsafeMetadata).toEqual({ position: 'goalie' });

await fakeUser.deleteIfExists();
});
});
1 change: 1 addition & 0 deletions packages/clerk-js/src/ui/components/SignIn/SignIn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ function SignInRoot() {
forceRedirectUrl: signInContext.signUpForceRedirectUrl,
fallbackRedirectUrl: signInContext.signUpFallbackRedirectUrl,
signInUrl: signInContext.signInUrl,
unsafeMetadata: signInContext.unsafeMetadata,
...normalizeRoutingOptions({ routing: signInContext?.routing, path: signInContext?.path }),
} as SignUpContextType;

Expand Down