From 12f92e9891bd1a9492c25186f8261428cf268b07 Mon Sep 17 00:00:00 2001 From: Robert Soriano Date: Wed, 11 Dec 2024 08:02:53 -0800 Subject: [PATCH] fix(astro): Prevent Netlify from doing a handshake redirect loop (#4745) --- .changeset/hungry-fishes-invent.md | 5 ++++ .../src/integration/create-integration.ts | 5 +++- packages/astro/src/internal/index.ts | 1 + .../astro/src/internal/remove-query-param.ts | 13 ++++++++++ packages/astro/src/server/clerk-middleware.ts | 24 ++++++++++++++++++- 5 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 .changeset/hungry-fishes-invent.md create mode 100644 packages/astro/src/internal/remove-query-param.ts diff --git a/.changeset/hungry-fishes-invent.md b/.changeset/hungry-fishes-invent.md new file mode 100644 index 0000000000..523dab125b --- /dev/null +++ b/.changeset/hungry-fishes-invent.md @@ -0,0 +1,5 @@ +--- +"@clerk/astro": patch +--- + +Fix handshake redirect loop in Netlify deployments diff --git a/packages/astro/src/integration/create-integration.ts b/packages/astro/src/integration/create-integration.ts index 70099d62d1..8d24fbc05e 100644 --- a/packages/astro/src/integration/create-integration.ts +++ b/packages/astro/src/integration/create-integration.ts @@ -109,7 +109,10 @@ function createIntegration() 'page', ` ${command === 'dev' ? `console.log("${packageName}","Initialize Clerk: page")` : ''} - import { runInjectionScript, swapDocument } from "${buildImportPath}"; + import { removeNetlifyCacheBustParam, runInjectionScript, swapDocument } from "${buildImportPath}"; + + // Fix an issue with infinite redirect in Netlify and Clerk dev instance + removeNetlifyCacheBustParam(); // Taken from https://github.com/withastro/astro/blob/e10b03e88c22592fbb42d7245b65c4f486ab736d/packages/astro/src/transitions/router.ts#L39. // Importing it directly from astro:transitions/client breaks custom client-side routing diff --git a/packages/astro/src/internal/index.ts b/packages/astro/src/internal/index.ts index 24c1b6670a..47ba4bf14a 100644 --- a/packages/astro/src/internal/index.ts +++ b/packages/astro/src/internal/index.ts @@ -14,3 +14,4 @@ export { runInjectionScript }; export { generateSafeId } from './utils/generateSafeId'; export { swapDocument } from './swap-document'; +export { NETLIFY_CACHE_BUST_PARAM, removeNetlifyCacheBustParam } from './remove-query-param'; diff --git a/packages/astro/src/internal/remove-query-param.ts b/packages/astro/src/internal/remove-query-param.ts new file mode 100644 index 0000000000..18624d8657 --- /dev/null +++ b/packages/astro/src/internal/remove-query-param.ts @@ -0,0 +1,13 @@ +export const NETLIFY_CACHE_BUST_PARAM = '__netlify_clerk_cache_bust'; + +/** + * Removes the temporary cache bust parameter that prevents infinite redirects + * in Netlify and Clerk's dev instance. + */ +export function removeNetlifyCacheBustParam() { + const url = new URL(window.location.href); + if (url.searchParams.has(NETLIFY_CACHE_BUST_PARAM)) { + url.searchParams.delete(NETLIFY_CACHE_BUST_PARAM); + window.history.replaceState(window.history.state, '', url); + } +} diff --git a/packages/astro/src/server/clerk-middleware.ts b/packages/astro/src/server/clerk-middleware.ts index 3bdf0a53cf..f56a0e7c6b 100644 --- a/packages/astro/src/server/clerk-middleware.ts +++ b/packages/astro/src/server/clerk-middleware.ts @@ -1,7 +1,7 @@ import type { AuthObject, ClerkClient } from '@clerk/backend'; import type { AuthenticateRequestOptions, ClerkRequest, RedirectFun, RequestState } from '@clerk/backend/internal'; import { AuthStatus, constants, createClerkRequest, createRedirect } from '@clerk/backend/internal'; -import { isDevelopmentFromSecretKey } from '@clerk/shared/keys'; +import { isDevelopmentFromPublishableKey, isDevelopmentFromSecretKey } from '@clerk/shared/keys'; import { isHttpOrHttps } from '@clerk/shared/proxy'; import { eventMethodCalled } from '@clerk/shared/telemetry'; import { handleValueOrFn } from '@clerk/shared/utils'; @@ -10,6 +10,7 @@ import type { APIContext } from 'astro'; // @ts-ignore import { authAsyncStorage } from '#async-local-storage'; +import { NETLIFY_CACHE_BUST_PARAM } from '../internal'; import { buildClerkHotloadScript } from './build-clerk-hotload-script'; import { clerkClient } from './clerk-client'; import { createCurrentUser } from './current-user'; @@ -83,6 +84,8 @@ export const clerkMiddleware: ClerkMiddleware = (...args: unknown[]): any => { const locationHeader = requestState.headers.get(constants.Headers.Location); if (locationHeader) { + handleNetlifyCacheInDevInstance(locationHeader, requestState); + const res = new Response(null, { status: 307, headers: requestState.headers }); return decorateResponseWithObservabilityHeaders(res, requestState); } else if (requestState.status === AuthStatus.Handshake) { @@ -234,6 +237,25 @@ Check if signInUrl is missing from your configuration or if it is not an absolut PUBLIC_CLERK_SIGN_IN_URL='SOME_URL' PUBLIC_CLERK_IS_SATELLITE='true'`; +/** + * Prevents infinite redirects in Netlify's functions + * by adding a cache bust parameter to the original redirect URL. This ensures Netlify + * doesn't serve a cached response during the authentication flow. + */ +function handleNetlifyCacheInDevInstance(locationHeader: string, requestState: RequestState) { + // Only run on Netlify environment and Clerk development instance + // eslint-disable-next-line turbo/no-undeclared-env-vars + if (import.meta.env.NETLIFY && isDevelopmentFromPublishableKey(requestState.publishableKey)) { + const hasHandshakeQueryParam = locationHeader.includes('__clerk_handshake'); + // If location header is the original URL before the handshake redirects, add cache bust param + if (!hasHandshakeQueryParam) { + const url = new URL(locationHeader); + url.searchParams.append(NETLIFY_CACHE_BUST_PARAM, Date.now().toString()); + requestState.headers.set('Location', url.toString()); + } + } +} + function decorateAstroLocal(clerkRequest: ClerkRequest, context: APIContext, requestState: RequestState) { const { reason, message, status, token } = requestState; context.locals.authToken = token;