Skip to content

Commit

Permalink
fix(clerk-js,shared): Remove __clerk_db_jwt from hash (#2858)
Browse files Browse the repository at this point in the history
on clerk-js init

Update the debBrowser handling logic to remove hash-based devBrowser JWTs from the URL. Even if v5 does not use the hash-based JWT at all, we still need to remove it from the URL in case clerk-js is initialised on a page after a redirect from an older clerk-js version, such as an AccountPortal using the v4 components
  • Loading branch information
nikosdouvlis authored Feb 26, 2024
1 parent 388070e commit 12f3c5c
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 41 deletions.
6 changes: 6 additions & 0 deletions .changeset/happy-points-relax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@clerk/clerk-js': patch
'@clerk/shared': patch
---

Update the debBrowser handling logic to remove hash-based devBrowser JWTs from the URL. Even if v5 does not use the hash-based JWT at all, we still need to remove it from the URL in case clerk-js is initialised on a page after a redirect from an older clerk-js version, such as an AccountPortal using the v4 components
6 changes: 3 additions & 3 deletions packages/clerk-js/src/core/devBrowser.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DEV_BROWSER_JWT_HEADER, getDevBrowserJWTFromURL, setDevBrowserJWTInURL } from '@clerk/shared/devBrowser';
import { DEV_BROWSER_JWT_HEADER, extractDevBrowserJWTFromURL, setDevBrowserJWTInURL } from '@clerk/shared/devBrowser';
import { parseErrors } from '@clerk/shared/error';
import type { ClerkAPIErrorJSON } from '@clerk/types';

Expand Down Expand Up @@ -61,8 +61,8 @@ export function createDevBrowser({ frontendApi, fapiClient }: CreateDevBrowserOp
}
});

// 1. Get the JWT from hash or search parameters when the redirection comes from AP
const devBrowserToken = getDevBrowserJWTFromURL(new URL(window.location.href));
// 1. Get the JWT from search parameters when the redirection comes from AP
const devBrowserToken = extractDevBrowserJWTFromURL(new URL(window.location.href));
if (devBrowserToken) {
setDevBrowserJWT(devBrowserToken);
return;
Expand Down
41 changes: 18 additions & 23 deletions packages/shared/src/__tests__/devbrowser.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getDevBrowserJWTFromURL, setDevBrowserJWTInURL } from '../devBrowser';
import { extractDevBrowserJWTFromURL, setDevBrowserJWTInURL } from '../devBrowser';

const DUMMY_URL_BASE = 'http://clerk-dummy';

Expand Down Expand Up @@ -50,34 +50,29 @@ describe('getDevBrowserJWTFromURL(url)', () => {
});

it('does not replaceState if the url does not contain a dev browser JWT', () => {
expect(getDevBrowserJWTFromURL(new URL('/foo', DUMMY_URL_BASE))).toEqual('');
expect(extractDevBrowserJWTFromURL(new URL('/foo', DUMMY_URL_BASE))).toEqual('');
expect(replaceStateMock).not.toHaveBeenCalled();
});

const testCases: Array<[string, string, null | string]> = [
['', '', null],
['foo', '', null],
['?__clerk_db_jwt=deadbeef', 'deadbeef', ''],
['foo?__clerk_db_jwt=deadbeef', 'deadbeef', 'foo'],
['/foo?__clerk_db_jwt=deadbeef', 'deadbeef', '/foo'],
['?__clerk_db_jwt=deadbeef#foo', 'deadbeef', '#foo'],
[
'/foo?bar=42&__clerk_db_jwt=deadbeef#qux__clerk_db_jwt[deadbeef2]',
'deadbeef',
'/foo?bar=42#qux__clerk_db_jwt[deadbeef2]',
],
it('does call replaceState if the url contains a dev browser JWT', () => {
expect(extractDevBrowserJWTFromURL(new URL('/foo?__clerk_db_jwt=token', DUMMY_URL_BASE))).toEqual('token');
expect(replaceStateMock).toHaveBeenCalled();
});

const testCases: Array<[string, string]> = [
['', ''],
['foo', ''],
['?__clerk_db_jwt=token', 'token'],
['foo?__clerk_db_jwt=token', 'token'],
['/foo?__clerk_db_jwt=token', 'token'],
['?__clerk_db_jwt=token#foo', 'token'],
['/foo?bar=42&__clerk_db_jwt=token#qux__clerk_db_jwt[token2]', 'token'],
];

test.each(testCases)(
'returns the dev browser JWT from a url. Params: url=(%s), jwt=(%s)',
(input, jwt, calledWith) => {
expect(getDevBrowserJWTFromURL(new URL(input, DUMMY_URL_BASE))).toEqual(jwt);

if (calledWith === null) {
expect(replaceStateMock).not.toHaveBeenCalled();
} else {
expect(replaceStateMock).toHaveBeenCalledWith(null, '', new URL(calledWith, DUMMY_URL_BASE).href);
}
'returns the dev browser JWT from a url and cleans all dev . Params: url=(%s), jwt=(%s)',
(input, jwt) => {
expect(extractDevBrowserJWTFromURL(new URL(input, DUMMY_URL_BASE))).toEqual(jwt);
},
);
});
57 changes: 43 additions & 14 deletions packages/shared/src/devBrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,49 @@ export function setDevBrowserJWTInURL(url: URL, jwt: string): URL {
return resultURL;
}

// Gets the dev_browser JWT from either the hash or the search
// Side effect:
// Removes dev_browser JWT from the URL as a side effect and updates the browser history
export function getDevBrowserJWTFromURL(url: URL): string {
const resultURL = new URL(url);

// extract & strip existing jwt from search
const jwt = resultURL.searchParams.get(DEV_BROWSER_JWT_KEY) || '';
resultURL.searchParams.delete(DEV_BROWSER_JWT_KEY);

// eslint-disable-next-line valid-typeof
if (jwt && typeof globalThis.history !== undefined) {
globalThis.history.replaceState(null, '', resultURL.href);
/**
* Gets the __clerk_db_jwt JWT from either the hash or the search
* Side effect:
* Removes __clerk_db_jwt JWT from the URL (hash and searchParams) and updates the browser history
*/
export function extractDevBrowserJWTFromURL(url: URL): string {
const jwt = readDevBrowserJwtFromSearchParams(url);
if (jwt && typeof globalThis.history !== 'undefined') {
globalThis.history.replaceState(null, '', removeDevBrowserJwt(url));
}

return jwt;
}

const readDevBrowserJwtFromSearchParams = (url: URL) => {
return url.searchParams.get(DEV_BROWSER_JWT_KEY) || '';
};

const removeDevBrowserJwt = (url: URL) => {
return removeDevBrowserJwtFromURLSearchParams(removeLegacyDevBrowserJwtFromURLHash(new URL(url)));
};

const removeDevBrowserJwtFromURLSearchParams = (_url: URL) => {
const url = new URL(_url);
url.searchParams.delete(DEV_BROWSER_JWT_KEY);
return url;
};

/**
* Removes the __clerk_db_jwt JWT from the URL hash.
* We no longer need to use this value, however, we should remove it from the URL
* Existing v4 apps will write the JWT to the hash and the search params in order to ensure
* backwards compatibility with older v4 apps.
* The only use case where this is needed now is when a user upgrades to clerk@5 locally
* without changing the component's version on their dashboard.
* In this scenario, the AP@4 -> localhost@5 redirect will still have the JWT in the hash,
* in which case we need to remove it.
*/
const removeLegacyDevBrowserJwtFromURLHash = (_url: URL) => {
const DEV_BROWSER_JWT_MARKER_REGEXP = /__clerk_db_jwt\[(.*)\]/;
const url = new URL(_url);
url.hash = url.hash.replace(DEV_BROWSER_JWT_MARKER_REGEXP, '');
if (url.href.endsWith('#')) {
url.hash = '';
}
return url;
};
2 changes: 1 addition & 1 deletion packages/shared/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,4 @@ export * from './underscore';
export * from './url';
export * from './object';
export { createWorkerTimers } from './workerTimers';
export { DEV_BROWSER_JWT_KEY, getDevBrowserJWTFromURL, setDevBrowserJWTInURL } from './devBrowser';
export { DEV_BROWSER_JWT_KEY, extractDevBrowserJWTFromURL, setDevBrowserJWTInURL } from './devBrowser';

0 comments on commit 12f3c5c

Please sign in to comment.