From 2e2864f7429fe683e527da54f8b2bdffc8e015dd Mon Sep 17 00:00:00 2001 From: George Desipris Date: Sat, 7 Oct 2023 17:56:04 +0300 Subject: [PATCH] feat(clerk-js,backend): Throw error if signInUrl is on same origin as satellite app --- .changeset/polite-rats-care.md | 6 ++++++ packages/backend/src/tokens/request.test.ts | 4 ++++ packages/backend/src/tokens/request.ts | 16 ++++++++++++++++ packages/clerk-js/src/core/clerk.ts | 16 ++++++++++++++++ packages/clerk-js/src/core/errors.ts | 8 ++++++++ 5 files changed, 50 insertions(+) create mode 100644 .changeset/polite-rats-care.md diff --git a/.changeset/polite-rats-care.md b/.changeset/polite-rats-care.md new file mode 100644 index 0000000000..60b94c40a3 --- /dev/null +++ b/.changeset/polite-rats-care.md @@ -0,0 +1,6 @@ +--- +'@clerk/clerk-js': patch +'@clerk/backend': patch +--- + +Throw an error if the `signInUrl` is on the same origin of a satellite application or if it is of invalid format diff --git a/packages/backend/src/tokens/request.test.ts b/packages/backend/src/tokens/request.test.ts index e90a76f50e..2bde827ece 100644 --- a/packages/backend/src/tokens/request.test.ts +++ b/packages/backend/src/tokens/request.test.ts @@ -319,12 +319,14 @@ export default (QUnit: QUnit) => { apiKey: 'deadbeef', clientUat: '0', isSatellite: true, + signInUrl: 'https://primary.dev/sign-in', domain: 'satellite.dev', }); assertInterstitial(assert, requestState, { reason: AuthErrorReason.SatelliteCookieNeedsSyncing, isSatellite: true, + signInUrl: 'https://primary.dev/sign-in', domain: 'satellite.dev', }); assert.equal(requestState.message, ''); @@ -337,6 +339,7 @@ export default (QUnit: QUnit) => { apiKey: 'deadbeef', clientUat: '0', isSatellite: true, + signInUrl: 'https://primary.dev/sign-in', domain: 'satellite.dev', userAgent: '[some-agent]', }); @@ -344,6 +347,7 @@ export default (QUnit: QUnit) => { assertSignedOut(assert, requestState, { reason: AuthErrorReason.SatelliteCookieNeedsSyncing, isSatellite: true, + signInUrl: 'https://primary.dev/sign-in', domain: 'satellite.dev', }); assertSignedOutToAuth(assert, requestState); diff --git a/packages/backend/src/tokens/request.ts b/packages/backend/src/tokens/request.ts index 979ae85d32..c0a03c5c5d 100644 --- a/packages/backend/src/tokens/request.ts +++ b/packages/backend/src/tokens/request.ts @@ -102,6 +102,19 @@ function assertProxyUrlOrDomain(proxyUrlOrDomain: string | undefined) { } } +function assertSignInUrlFormatAndOrigin(_signInUrl: string, hostname: string) { + let signInUrl: URL; + try { + signInUrl = new URL(_signInUrl); + } catch { + throw new Error(`The signInUrl needs to have a absolute url format.`); + } + + if (signInUrl.hostname === hostname) { + throw new Error(`The signInUrl needs to be on a different origin than your satellite application.`); + } +} + export async function authenticateRequest(options: AuthenticateRequestOptions): Promise { const { cookies, headers, searchParams } = buildRequest(options?.request); @@ -128,6 +141,9 @@ export async function authenticateRequest(options: AuthenticateRequestOptions): if (options.isSatellite) { assertSignInUrlExists(options.signInUrl, (options.secretKey || options.apiKey) as string); + if (options.signInUrl && options.origin /* could this actually be undefined? */) { + assertSignInUrlFormatAndOrigin(options.signInUrl, options.origin); + } assertProxyUrlOrDomain(options.proxyUrl || options.domain); } diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 93262f0a48..850092124f 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -94,6 +94,8 @@ import type { DevBrowserHandler } from './devBrowserHandler'; import createDevBrowserHandler from './devBrowserHandler'; import { clerkErrorInitFailed, + clerkInvalidSignInUrlFormat, + clerkInvalidSignInUrlOrigin, clerkMissingDevBrowserJwt, clerkMissingProxyUrlAndDomain, clerkMissingSignInUrlAsSatellite, @@ -1280,6 +1282,7 @@ export default class Clerk implements ClerkInterface { if (!this.isSatellite) { return; } + if (this.#instanceType === 'development' && !this.#options.signInUrl) { clerkMissingSignInUrlAsSatellite(); } @@ -1287,6 +1290,19 @@ export default class Clerk implements ClerkInterface { if (!this.proxyUrl && !this.domain) { clerkMissingProxyUrlAndDomain(); } + + if (this.#options.signInUrl) { + let signInUrl: URL; + try { + signInUrl = new URL(this.#options.signInUrl); + } catch { + clerkInvalidSignInUrlFormat(); + } + + if (signInUrl.hostname === this.domain.replace('clerk.', '')) { + clerkInvalidSignInUrlOrigin(); + } + } }; #loadInStandardBrowser = async (): Promise => { diff --git a/packages/clerk-js/src/core/errors.ts b/packages/clerk-js/src/core/errors.ts index 4c405e6bf1..cf3171782c 100644 --- a/packages/clerk-js/src/core/errors.ts +++ b/packages/clerk-js/src/core/errors.ts @@ -94,6 +94,14 @@ export function clerkMissingProxyUrlAndDomain(): never { ); } +export function clerkInvalidSignInUrlOrigin(): never { + throw new Error(`${errorPrefix} The signInUrl needs to be on a different origin than your satellite application.`); +} + +export function clerkInvalidSignInUrlFormat(): never { + throw new Error(`${errorPrefix} The signInUrl needs to have a absolute url format.`); +} + export function clerkMissingSignInUrlAsSatellite(): never { throw new Error( `${errorPrefix} Missing signInUrl. A satellite application needs to specify the signInUrl for development instances.`,