From a180638091021d8acbd3436e85b5d5d9cd358780 Mon Sep 17 00:00:00 2001 From: Jacek Date: Fri, 1 Aug 2025 11:06:19 -0500 Subject: [PATCH] fix: multisession sign in redirect --- .../ui/common/__tests__/withRedirect.test.tsx | 89 +++++++++++++++++++ .../clerk-js/src/ui/common/withRedirect.tsx | 24 ++++- .../clerk-js/src/utils/componentGuards.ts | 11 +++ 3 files changed, 121 insertions(+), 3 deletions(-) diff --git a/packages/clerk-js/src/ui/common/__tests__/withRedirect.test.tsx b/packages/clerk-js/src/ui/common/__tests__/withRedirect.test.tsx index e823455c560..793b4d0e9fc 100644 --- a/packages/clerk-js/src/ui/common/__tests__/withRedirect.test.tsx +++ b/packages/clerk-js/src/ui/common/__tests__/withRedirect.test.tsx @@ -3,6 +3,7 @@ import React from 'react'; import { render } from '../../../testUtils'; import { bindCreateFixtures } from '../../utils/test/createFixtures'; import { withRedirect } from '../withRedirect'; +import { sessionExistsAndRedirectUrlPresent } from '../../../utils/componentGuards'; const { createFixtures } = bindCreateFixtures('SignIn'); @@ -41,3 +42,91 @@ describe('withRedirect', () => { expect(fixtures.router.navigate).not.toHaveBeenCalledWith('/'); }); }); + +describe('sessionExistsAndRedirectUrlPresent', () => { + beforeEach(() => { + // Set up window.location.href for tests + Object.defineProperty(window, 'location', { + value: { href: 'http://test.host/' }, + writable: true, + }); + }); + + it('returns true when user is signed in and redirect_url is present', async () => { + const { wrapper, fixtures } = await createFixtures(f => { + f.withUser({}); + }); + + // Mock window.location.search to include redirect_url + Object.defineProperty(window, 'location', { + value: { + href: 'http://test.host/', + search: '?redirect_url=https%3A%2F%2Fexample.com' + }, + writable: true, + }); + + const TestComponent = () =>
Test
; + const WithHOC = withRedirect( + TestComponent, + sessionExistsAndRedirectUrlPresent, + () => '/redirected', + ); + + render(, { wrapper }); + + expect(fixtures.router.navigate).toHaveBeenCalledWith('/redirected'); + }); + + it('returns false when user is not signed in', async () => { + const { wrapper, fixtures } = await createFixtures(f => { + // No user signed in + }); + + // Mock window.location.search to include redirect_url + Object.defineProperty(window, 'location', { + value: { + href: 'http://test.host/', + search: '?redirect_url=https%3A%2F%2Fexample.com' + }, + writable: true, + }); + + const TestComponent = () =>
Test
; + const WithHOC = withRedirect( + TestComponent, + sessionExistsAndRedirectUrlPresent, + () => '/redirected', + ); + + render(, { wrapper }); + + expect(fixtures.router.navigate).not.toHaveBeenCalled(); + }); + + it('returns false when redirect_url is not present', async () => { + const { wrapper, fixtures } = await createFixtures(f => { + f.withUser({}); + }); + + // Mock window.location.search without redirect_url + Object.defineProperty(window, 'location', { + value: { + href: 'http://test.host/', + search: '?other_param=value' + }, + writable: true, + }); + + const TestComponent = () =>
Test
; + const WithHOC = withRedirect( + TestComponent, + sessionExistsAndRedirectUrlPresent, + () => '/redirected', + ); + + render(, { wrapper }); + + expect(fixtures.router.navigate).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/clerk-js/src/ui/common/withRedirect.tsx b/packages/clerk-js/src/ui/common/withRedirect.tsx index 81336a63ec2..260cff48de5 100644 --- a/packages/clerk-js/src/ui/common/withRedirect.tsx +++ b/packages/clerk-js/src/ui/common/withRedirect.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { warnings } from '../../core/warnings'; import type { ComponentGuard } from '../../utils'; -import { sessionExistsAndSingleSessionModeEnabled } from '../../utils'; +import { sessionExistsAndSingleSessionModeEnabled, sessionExistsAndRedirectUrlPresent } from '../../utils'; import { useEnvironment, useOptions, useSignInContext, useSignUpContext } from '../contexts'; import { useRouter } from '../router'; import type { AvailableComponentProps } from '../types'; @@ -58,9 +58,18 @@ export const withRedirectToAfterSignIn =

(Com const HOC = (props: P) => { const signInCtx = useSignInContext(); + + // Combined guard: redirect if user is signed in AND either: + // 1. Single session mode is enabled, OR + // 2. There's a redirect_url in the query parameters + const combinedGuard: ComponentGuard = (clerk, environment) => { + return sessionExistsAndSingleSessionModeEnabled(clerk, environment) || + sessionExistsAndRedirectUrlPresent(clerk); + }; + return withRedirect( Component, - sessionExistsAndSingleSessionModeEnabled, + combinedGuard, ({ clerk }) => signInCtx.sessionTaskUrl || signInCtx.afterSignInUrl || clerk.buildAfterSignInUrl(), signInCtx.sessionTaskUrl ? warnings.cannotRenderSignInComponentWhenTaskExists @@ -79,9 +88,18 @@ export const withRedirectToAfterSignUp =

(Com const HOC = (props: P) => { const signUpCtx = useSignUpContext(); + + // Combined guard: redirect if user is signed in AND either: + // 1. Single session mode is enabled, OR + // 2. There's a redirect_url in the query parameters + const combinedGuard: ComponentGuard = (clerk, environment) => { + return sessionExistsAndSingleSessionModeEnabled(clerk, environment) || + sessionExistsAndRedirectUrlPresent(clerk); + }; + return withRedirect( Component, - sessionExistsAndSingleSessionModeEnabled, + combinedGuard, ({ clerk }) => signUpCtx.sessionTaskUrl || signUpCtx.afterSignUpUrl || clerk.buildAfterSignUpUrl(), signUpCtx.sessionTaskUrl ? warnings.cannotRenderSignUpComponentWhenTaskExists diff --git a/packages/clerk-js/src/utils/componentGuards.ts b/packages/clerk-js/src/utils/componentGuards.ts index baf23904b17..d9f7a72d6aa 100644 --- a/packages/clerk-js/src/utils/componentGuards.ts +++ b/packages/clerk-js/src/utils/componentGuards.ts @@ -10,6 +10,17 @@ export const sessionExistsAndSingleSessionModeEnabled: ComponentGuard = (clerk, return !!(clerk.session && environment?.authConfig.singleSessionMode); }; +export const sessionExistsAndRedirectUrlPresent: ComponentGuard = (clerk) => { + if (!clerk.session) { + return false; + } + + const urlParams = new URLSearchParams(window.location.search); + const redirectUrl = urlParams.get('redirect_url'); + + return !!redirectUrl; +}; + export const noUserExists: ComponentGuard = clerk => { return !clerk.user; };