From 21a4f1e0e497d9a7f165dd2777129d26c9bba9f1 Mon Sep 17 00:00:00 2001
From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com>
Date: Fri, 21 Jun 2024 16:04:53 -0300
Subject: [PATCH 1/9] Introduce ALS store for request data within middleware
runtime
---
packages/nextjs/src/server/clerkClient.ts | 65 +++++++-
packages/nextjs/src/server/clerkMiddleware.ts | 146 +++++++++---------
2 files changed, 140 insertions(+), 71 deletions(-)
diff --git a/packages/nextjs/src/server/clerkClient.ts b/packages/nextjs/src/server/clerkClient.ts
index 41b6559b5a3..7ef41d9f3c7 100644
--- a/packages/nextjs/src/server/clerkClient.ts
+++ b/packages/nextjs/src/server/clerkClient.ts
@@ -1,5 +1,10 @@
+import type { ClerkClient } from '@clerk/backend';
import { createClerkClient } from '@clerk/backend';
+import type { AuthenticateRequestOptions } from '@clerk/backend/internal';
+import { constants } from '@clerk/backend/internal';
+import { deprecated } from '@clerk/shared/deprecated';
+import { buildRequestLike } from '../app-router/server/utils';
import {
API_URL,
API_VERSION,
@@ -12,8 +17,9 @@ import {
TELEMETRY_DEBUG,
TELEMETRY_DISABLED,
} from './constants';
+import { decryptClerkRequestData, getHeader } from './utils';
-const clerkClient = createClerkClient({
+const clerkClientDefaultOptions = {
secretKey: SECRET_KEY,
publishableKey: PUBLISHABLE_KEY,
apiUrl: API_URL,
@@ -27,6 +33,63 @@ const clerkClient = createClerkClient({
disabled: TELEMETRY_DISABLED,
debug: TELEMETRY_DEBUG,
},
+};
+
+export const clerkClientStorage = new AsyncLocalStorage>();
+
+const createClerkClientWithOptions: typeof createClerkClient = options =>
+ createClerkClient({ ...clerkClientDefaultOptions, ...options });
+
+const clerkClientSingleton = createClerkClient(clerkClientDefaultOptions);
+
+/**
+ * @deprecated
+ * This singleton is deprecated and will be removed in a future release. Please use `clerkClient()` as a function instead.
+ */
+const clerkClientSingletonProxy = new Proxy(clerkClientSingleton, {
+ get(target, prop, receiver) {
+ deprecated('clerkClient object', 'Use `clerkClient()` as a function instead.');
+
+ return Reflect.get(target, prop, receiver);
+ },
});
+/**
+ * Constructs a BAPI client that accesses request data within the runtime.
+ * Necessary if middleware dynamic keys are used.
+ */
+const clerkClientForRequest = () => {
+ let requestData: Partial | undefined;
+
+ /**
+ * For BAPI client usage inside middleware runtime, fallbacks to AsyncLocalStorage to access request data
+ */
+ const clerkClientStore = clerkClientStorage.getStore();
+ if (clerkClientStore) {
+ requestData = clerkClientStore;
+ } else {
+ /**
+ * For BAPI usage from application server, fallbacks to access request data via `NextRequest`
+ */
+ const request = buildRequestLike();
+ const encryptedRequestData = getHeader(request, constants.Headers.ClerkRequestData);
+ requestData = decryptClerkRequestData(encryptedRequestData);
+ }
+
+ if (requestData?.secretKey || requestData?.publishableKey) {
+ return createClerkClientWithOptions({
+ secretKey: requestData.secretKey,
+ publishableKey: requestData.publishableKey,
+ });
+ }
+
+ return clerkClient;
+};
+
+const clerkClient: ClerkClient & typeof clerkClientForRequest = Object.assign(
+ clerkClientForRequest,
+ // TODO SDK-1839 - Remove `clerkClient` singleton in the next major version of `@clerk/nextjs`
+ clerkClientSingletonProxy,
+);
+
export { clerkClient };
diff --git a/packages/nextjs/src/server/clerkMiddleware.ts b/packages/nextjs/src/server/clerkMiddleware.ts
index 8d4a00dcad8..6de46fe0283 100644
--- a/packages/nextjs/src/server/clerkMiddleware.ts
+++ b/packages/nextjs/src/server/clerkMiddleware.ts
@@ -12,7 +12,7 @@ import { NextResponse } from 'next/server';
import { isRedirect, serverRedirectWithAuth, setHeader } from '../utils';
import { withLogger } from '../utils/debugLogger';
-import { clerkClient } from './clerkClient';
+import { clerkClient, clerkClientStorage } from './clerkClient';
import { PUBLISHABLE_KEY, SECRET_KEY, SIGN_IN_URL, SIGN_UP_URL } from './constants';
import { errorThrower } from './errorThrower';
import type { AuthProtect } from './protect';
@@ -91,82 +91,88 @@ export const clerkMiddleware: ClerkMiddleware = withLogger('clerkMiddleware', lo
signUpUrl,
};
- clerkClient.telemetry.record(
- eventMethodCalled('clerkMiddleware', {
- handler: Boolean(handler),
- satellite: Boolean(options.isSatellite),
- proxy: Boolean(options.proxyUrl),
- }),
- );
-
- const nextMiddleware: NextMiddleware = async (request, event) => {
- const clerkRequest = createClerkRequest(request);
- logger.debug('options', options);
- logger.debug('url', () => clerkRequest.toJSON());
-
- const requestState = await clerkClient.authenticateRequest(
- clerkRequest,
- createAuthenticateRequestOptions(clerkRequest, options),
+ return clerkClientStorage.run(options, () => {
+ clerkClient().telemetry.record(
+ eventMethodCalled('clerkMiddleware', {
+ handler: Boolean(handler),
+ satellite: Boolean(options.isSatellite),
+ proxy: Boolean(options.proxyUrl),
+ }),
);
- logger.debug('requestState', () => ({
- status: requestState.status,
- headers: JSON.stringify(Object.fromEntries(requestState.headers)),
- reason: requestState.reason,
- }));
-
- const locationHeader = requestState.headers.get(constants.Headers.Location);
- if (locationHeader) {
- return new Response(null, { status: 307, headers: requestState.headers });
- } else if (requestState.status === AuthStatus.Handshake) {
- throw new Error('Clerk: handshake status without redirect');
- }
-
- const authObject = requestState.toAuth();
- logger.debug('auth', () => ({ auth: authObject, debug: authObject.debug() }));
+ const nextMiddleware: NextMiddleware = async (request, event) => {
+ const clerkRequest = createClerkRequest(request);
+ logger.debug('options', options);
+ logger.debug('url', () => clerkRequest.toJSON());
- const redirectToSignIn = createMiddlewareRedirectToSignIn(clerkRequest);
- const protect = createMiddlewareProtect(clerkRequest, authObject, redirectToSignIn);
- const authObjWithMethods: ClerkMiddlewareAuthObject = Object.assign(authObject, { protect, redirectToSignIn });
-
- let handlerResult: Response = NextResponse.next();
- try {
- handlerResult = (await handler?.(() => authObjWithMethods, request, event)) || handlerResult;
- } catch (e: any) {
- handlerResult = handleControlFlowErrors(e, clerkRequest, requestState);
- }
-
- // TODO @nikos: we need to make this more generic
- // and move the logic in clerk/backend
- if (requestState.headers) {
- requestState.headers.forEach((value, key) => {
- handlerResult.headers.append(key, value);
- });
- }
+ const requestState = await clerkClient().authenticateRequest(
+ clerkRequest,
+ createAuthenticateRequestOptions(clerkRequest, options),
+ );
- if (isRedirect(handlerResult)) {
- logger.debug('handlerResult is redirect');
- return serverRedirectWithAuth(clerkRequest, handlerResult, options);
- }
+ logger.debug('requestState', () => ({
+ status: requestState.status,
+ headers: JSON.stringify(Object.fromEntries(requestState.headers)),
+ reason: requestState.reason,
+ }));
+
+ const locationHeader = requestState.headers.get(constants.Headers.Location);
+ if (locationHeader) {
+ return new Response(null, { status: 307, headers: requestState.headers });
+ } else if (requestState.status === AuthStatus.Handshake) {
+ throw new Error('Clerk: handshake status without redirect');
+ }
+
+ const authObject = requestState.toAuth();
+ logger.debug('auth', () => ({ auth: authObject, debug: authObject.debug() }));
+
+ const redirectToSignIn = createMiddlewareRedirectToSignIn(clerkRequest);
+ const protect = createMiddlewareProtect(clerkRequest, authObject, redirectToSignIn);
+ const authObjWithMethods: ClerkMiddlewareAuthObject = Object.assign(authObject, { protect, redirectToSignIn });
+
+ let handlerResult: Response = NextResponse.next();
+ try {
+ handlerResult =
+ (await clerkClientStorage.run(
+ options,
+ async () => await handler?.(() => authObjWithMethods, request, event),
+ )) || handlerResult;
+ } catch (e: any) {
+ handlerResult = handleControlFlowErrors(e, clerkRequest, requestState);
+ }
+
+ // TODO @nikos: we need to make this more generic
+ // and move the logic in clerk/backend
+ if (requestState.headers) {
+ requestState.headers.forEach((value, key) => {
+ handlerResult.headers.append(key, value);
+ });
+ }
+
+ if (isRedirect(handlerResult)) {
+ logger.debug('handlerResult is redirect');
+ return serverRedirectWithAuth(clerkRequest, handlerResult, options);
+ }
+
+ if (options.debug) {
+ setRequestHeadersOnNextResponse(handlerResult, clerkRequest, { [constants.Headers.EnableDebug]: 'true' });
+ }
+
+ decorateRequest(clerkRequest, handlerResult, requestState, options);
+
+ return handlerResult;
+ };
- if (options.debug) {
- setRequestHeadersOnNextResponse(handlerResult, clerkRequest, { [constants.Headers.EnableDebug]: 'true' });
+ // If we have a request and event, we're being called as a middleware directly
+ // eg, export default clerkMiddleware;
+ if (request && event) {
+ return nextMiddleware(request, event);
}
- decorateRequest(clerkRequest, handlerResult, requestState, { secretKey, signInUrl, signUpUrl, publishableKey });
-
- return handlerResult;
- };
-
- // If we have a request and event, we're being called as a middleware directly
- // eg, export default clerkMiddleware;
- if (request && event) {
- return nextMiddleware(request, event);
- }
-
- // Otherwise, return a middleware that can be called with a request and event
- // eg, export default clerkMiddleware(auth => { ... });
- return nextMiddleware;
+ // Otherwise, return a middleware that can be called with a request and event
+ // eg, export default clerkMiddleware(auth => { ... });
+ return nextMiddleware;
+ });
});
const parseRequestAndEvent = (args: unknown[]) => {
From 82f6fd23bf1675f41b9341e7f713f2a19012dbcc Mon Sep 17 00:00:00 2001
From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com>
Date: Tue, 25 Jun 2024 08:08:06 -0300
Subject: [PATCH 2/9] Fetch dynamic keys from `currentUser`
---
packages/nextjs/src/app-router/server/currentUser.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/nextjs/src/app-router/server/currentUser.ts b/packages/nextjs/src/app-router/server/currentUser.ts
index ae3a2090f85..4d7cb2d7be1 100644
--- a/packages/nextjs/src/app-router/server/currentUser.ts
+++ b/packages/nextjs/src/app-router/server/currentUser.ts
@@ -9,5 +9,5 @@ export async function currentUser(): Promise {
return null;
}
- return clerkClient.users.getUser(userId);
+ return clerkClient().users.getUser(userId);
}
From 4a4b66c5f797a4650c88612241af67c6bf122ef5 Mon Sep 17 00:00:00 2001
From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com>
Date: Tue, 25 Jun 2024 08:24:07 -0300
Subject: [PATCH 3/9] Call `clerkClient()` within tests
---
integration/tests/dynamic-keys.test.ts | 73 +++++++++++++++++++
.../server/__tests__/authMiddleware.test.ts | 22 +++---
.../src/server/__tests__/clerkClient.test.ts | 2 +-
.../server/__tests__/clerkMiddleware.test.ts | 34 ++++-----
packages/nextjs/src/server/authMiddleware.ts | 4 +-
packages/nextjs/src/server/clerkClient.ts | 6 +-
6 files changed, 108 insertions(+), 33 deletions(-)
create mode 100644 integration/tests/dynamic-keys.test.ts
diff --git a/integration/tests/dynamic-keys.test.ts b/integration/tests/dynamic-keys.test.ts
new file mode 100644
index 00000000000..1fe831755d1
--- /dev/null
+++ b/integration/tests/dynamic-keys.test.ts
@@ -0,0 +1,73 @@
+import { test } from '@playwright/test';
+
+import type { Application } from '../models/application';
+import { appConfigs } from '../presets';
+
+test.describe('dynamic keys @nextjs', () => {
+ test.describe.configure({ mode: 'parallel' });
+ let app: Application;
+
+ test.beforeAll(async () => {
+ app = await appConfigs.next.appRouter
+ .clone()
+ .addFile(
+ 'src/middleware.ts',
+ () => `import { clerkMiddleware } from '@clerk/nextjs/server';
+import { NextResponse } from "next/server";
+
+export default clerkMiddleware();
+
+export const config = {
+ matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
+};`,
+ )
+ .addFile(
+ 'src/app/provider.tsx',
+ () => `'use client'
+import { ClerkProvider } from "@clerk/nextjs"
+
+export function Provider({ children }: { children: any }) {
+ return (
+
+ {children}
+
+ )
+}`,
+ )
+ .addFile(
+ 'src/app/layout.tsx',
+ () => `import './globals.css';
+import { Inter } from 'next/font/google';
+import { Provider } from './provider';
+
+const inter = Inter({ subsets: ['latin'] });
+
+export const metadata = {
+ title: 'Create Next App',
+ description: 'Generated by create next app',
+};
+
+export default function RootLayout({ children }: { children: React.ReactNode }) {
+ return (
+
+
+ {children}
+
+
+ );
+}
+ `,
+ )
+ .commit();
+ await app.setup();
+ await app.withEnv(appConfigs.envs.withEmailCodes);
+ await app.dev();
+ });
+
+ test.afterAll(async () => {
+ await app.teardown();
+ });
+
+ // test('redirects to `signInUrl` on `auth.protect`')
+ // test('uses to `secretKey` for auth signature')
+});
diff --git a/packages/nextjs/src/server/__tests__/authMiddleware.test.ts b/packages/nextjs/src/server/__tests__/authMiddleware.test.ts
index 31deb0b2be6..b474ffea8a0 100644
--- a/packages/nextjs/src/server/__tests__/authMiddleware.test.ts
+++ b/packages/nextjs/src/server/__tests__/authMiddleware.test.ts
@@ -228,7 +228,7 @@ describe('authMiddleware(params)', () => {
})(mockRequest({ url: '/ignored' }), {} as NextFetchEvent);
expect(resp?.status).toEqual(200);
- expect(clerkClient.authenticateRequest).not.toBeCalled();
+ expect(clerkClient().authenticateRequest).not.toBeCalled();
expect(beforeAuthSpy).not.toBeCalled();
expect(afterAuthSpy).not.toBeCalled();
});
@@ -243,7 +243,7 @@ describe('authMiddleware(params)', () => {
})(mockRequest({ url: '/protected' }), {} as NextFetchEvent);
expect(resp?.status).toEqual(200);
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
expect(beforeAuthSpy).toBeCalled();
expect(afterAuthSpy).toBeCalled();
});
@@ -310,7 +310,7 @@ describe('authMiddleware(params)', () => {
expect(resp?.status).toEqual(200);
expect(resp?.headers.get('x-clerk-auth-reason')).toEqual('skip');
- expect(clerkClient.authenticateRequest).not.toBeCalled();
+ expect(clerkClient().authenticateRequest).not.toBeCalled();
expect(afterAuthSpy).not.toBeCalled();
});
@@ -322,7 +322,7 @@ describe('authMiddleware(params)', () => {
})(mockRequest({ url: '/protected' }), {} as NextFetchEvent);
expect(resp?.status).toEqual(200);
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
expect(afterAuthSpy).toBeCalled();
});
@@ -335,7 +335,7 @@ describe('authMiddleware(params)', () => {
expect(resp?.status).toEqual(307);
expect(resp?.headers.get('location')).toEqual('https://www.clerk.com/custom-redirect');
- expect(clerkClient.authenticateRequest).not.toBeCalled();
+ expect(clerkClient().authenticateRequest).not.toBeCalled();
expect(afterAuthSpy).not.toBeCalled();
});
@@ -358,7 +358,7 @@ describe('authMiddleware(params)', () => {
expect(resp?.status).toEqual(200);
expect(resp?.headers.get('x-before-auth-header')).toEqual('before');
expect(resp?.headers.get('x-after-auth-header')).toEqual('after');
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
});
});
@@ -372,7 +372,7 @@ describe('authMiddleware(params)', () => {
expect(resp?.headers.get('location')).toEqual(
'https://accounts.included.katydid-92.lcl.dev/sign-in?redirect_url=https%3A%2F%2Fwww.clerk.com%2Fprotected',
);
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
});
it('uses authenticateRequest result as auth', async () => {
@@ -383,7 +383,7 @@ describe('authMiddleware(params)', () => {
await authMiddleware({ afterAuth: afterAuthSpy })(req, event);
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
expect(afterAuthSpy).toBeCalledWith(
{
userId: null,
@@ -421,7 +421,7 @@ describe('Dev Browser JWT when redirecting to cross origin', function () {
expect(resp?.headers.get('location')).toEqual(
'https://accounts.included.katydid-92.lcl.dev/sign-in?redirect_url=https%3A%2F%2Fwww.clerk.com%2Fprotected',
);
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
});
it('appends the Dev Browser JWT to the search when cookie __clerk_db_jwt exists and location is an Account Portal URL', async () => {
@@ -433,7 +433,7 @@ describe('Dev Browser JWT when redirecting to cross origin', function () {
expect(resp?.headers.get('location')).toEqual(
'https://accounts.included.katydid-92.lcl.dev/sign-in?redirect_url=https%3A%2F%2Fwww.clerk.com%2Fprotected&__clerk_db_jwt=test_jwt',
);
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
});
it('does NOT append the Dev Browser JWT if x-clerk-redirect-to header is not set', async () => {
@@ -443,7 +443,7 @@ describe('Dev Browser JWT when redirecting to cross origin', function () {
expect(resp?.status).toEqual(307);
expect(resp?.headers.get('location')).toEqual('https://google.com/');
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
});
});
diff --git a/packages/nextjs/src/server/__tests__/clerkClient.test.ts b/packages/nextjs/src/server/__tests__/clerkClient.test.ts
index 1ceedefc0f7..58b6e883ddd 100644
--- a/packages/nextjs/src/server/__tests__/clerkClient.test.ts
+++ b/packages/nextjs/src/server/__tests__/clerkClient.test.ts
@@ -4,7 +4,7 @@ import { clerkClient } from '../clerkClient';
describe('clerkClient', () => {
it('should pass version package to userAgent', async () => {
- await clerkClient.users.getUser('user_test');
+ await clerkClient().users.getUser('user_test');
expect(global.fetch).toBeCalled();
expect((global.fetch as any).mock.calls[0][1].headers).toMatchObject({
diff --git a/packages/nextjs/src/server/__tests__/clerkMiddleware.test.ts b/packages/nextjs/src/server/__tests__/clerkMiddleware.test.ts
index d4e19aa1609..2763d06f9ba 100644
--- a/packages/nextjs/src/server/__tests__/clerkMiddleware.test.ts
+++ b/packages/nextjs/src/server/__tests__/clerkMiddleware.test.ts
@@ -253,7 +253,7 @@ describe('clerkMiddleware(params)', () => {
expect(resp?.status).toEqual(307);
expect(resp?.headers.get('location')).toContain('sign-in');
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
});
it('redirects to sign-in url when redirectToSignIn is called with the correct returnBackUrl', async () => {
@@ -270,7 +270,7 @@ describe('clerkMiddleware(params)', () => {
expect(resp?.status).toEqual(307);
expect(resp?.headers.get('location')).toContain('sign-in');
expect(new URL(resp!.headers.get('location')!).searchParams.get('redirect_url')).toContain('/protected');
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
});
it('redirects to sign-in url with redirect_url set to the provided returnBackUrl param', async () => {
@@ -289,7 +289,7 @@ describe('clerkMiddleware(params)', () => {
expect(new URL(resp!.headers.get('location')!).searchParams.get('redirect_url')).toEqual(
'https://www.clerk.com/hello',
);
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
});
it('redirects to sign-in url without a redirect_url when returnBackUrl is null', async () => {
@@ -306,7 +306,7 @@ describe('clerkMiddleware(params)', () => {
expect(resp?.status).toEqual(307);
expect(resp?.headers.get('location')).toContain('sign-in');
expect(new URL(resp!.headers.get('location')!).searchParams.get('redirect_url')).toBeNull();
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
});
});
@@ -331,7 +331,7 @@ describe('clerkMiddleware(params)', () => {
expect(resp?.status).toEqual(307);
expect(resp?.headers.get('location')).toContain('sign-in');
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
});
it('does not redirect to sign-in url when protect is called, the user is signed in and the request is a page request', async () => {
@@ -354,7 +354,7 @@ describe('clerkMiddleware(params)', () => {
expect(resp?.status).toEqual(200);
expect(resp?.headers.get('location')).toBeFalsy();
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
});
it('throws a not found error when protect is called, the user is signed out, and is not a page request', async () => {
@@ -377,7 +377,7 @@ describe('clerkMiddleware(params)', () => {
expect(resp?.status).toEqual(200);
expect(resp?.headers.get(constants.Headers.AuthReason)).toContain('protect-rewrite');
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
});
it('throws a not found error when protect is called with RBAC params the user does not fulfill, and is a page request', async () => {
@@ -400,7 +400,7 @@ describe('clerkMiddleware(params)', () => {
expect(resp?.status).toEqual(200);
expect(resp?.headers.get(constants.Headers.AuthReason)).toContain('protect-rewrite');
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
});
it('redirects to unauthenticatedUrl when protect is called with the redirectUrl param, the user is signed out, and is a page request', async () => {
@@ -424,7 +424,7 @@ describe('clerkMiddleware(params)', () => {
expect(resp?.status).toEqual(307);
expect(resp?.headers.get('location')).toEqual('https://www.clerk.com/hello');
expect(resp?.headers.get(constants.Headers.ClerkRedirectTo)).toEqual('true');
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
});
it('redirects to unauthorizedUrl when protect is called with the redirectUrl param, the user does not fulfill the RBAC params, and is a page request', async () => {
@@ -454,7 +454,7 @@ describe('clerkMiddleware(params)', () => {
expect(resp?.status).toEqual(307);
expect(resp?.headers.get('location')).toEqual('https://www.clerk.com/discover');
expect(resp?.headers.get(constants.Headers.ClerkRedirectTo)).toEqual('true');
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
});
});
@@ -479,7 +479,7 @@ describe('clerkMiddleware(params)', () => {
expect(resp?.status).toEqual(307);
expect(resp?.headers.get('location')).toContain('sign-in');
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
});
it('forwards headers from authenticateRequest when auth().protect() is called', async () => {
@@ -507,7 +507,7 @@ describe('clerkMiddleware(params)', () => {
expect(resp?.headers.get('X-Clerk-Auth')).toEqual('1');
expect(resp?.headers.get('Set-Cookie')).toEqual('session=;');
expect(resp?.headers.get('location')).toContain('sign-in');
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
});
it('redirects to unauthenticatedUrl when protect is called with the unauthenticatedUrl param, the user is signed out, and is a page request', async () => {
@@ -534,7 +534,7 @@ describe('clerkMiddleware(params)', () => {
expect(resp?.status).toEqual(307);
expect(resp?.headers.get('location')).toContain('https://www.clerk.com/unauthenticatedUrl');
expect(resp?.headers.get(constants.Headers.ClerkRedirectTo)).toEqual('true');
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
});
it('redirects to unauthorizedUrl when protect is called with the unauthorizedUrl param, the user is signed in but does not have permissions, and is a page request', async () => {
@@ -564,7 +564,7 @@ describe('clerkMiddleware(params)', () => {
expect(resp?.status).toEqual(307);
expect(resp?.headers.get('location')).toContain('https://www.clerk.com/unauthorizedUrl');
expect(resp?.headers.get(constants.Headers.ClerkRedirectTo)).toEqual('true');
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
});
});
});
@@ -592,7 +592,7 @@ describe('Dev Browser JWT when redirecting to cross origin for page requests', f
expect(resp?.headers.get('location')).toEqual(
'https://accounts.included.katydid-92.lcl.dev/sign-in?redirect_url=https%3A%2F%2Fwww.clerk.com%2Fprotected',
);
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
});
it('appends the Dev Browser JWT to the search when cookie __clerk_db_jwt exists and location is an Account Portal URL', async () => {
@@ -616,7 +616,7 @@ describe('Dev Browser JWT when redirecting to cross origin for page requests', f
expect(resp?.headers.get('location')).toEqual(
'https://accounts.included.katydid-92.lcl.dev/sign-in?redirect_url=https%3A%2F%2Fwww.clerk.com%2Fprotected&__clerk_db_jwt=test_jwt',
);
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
});
it('does NOT append the Dev Browser JWT if x-clerk-redirect-to header is not set (user-returned redirect)', async () => {
@@ -642,6 +642,6 @@ describe('Dev Browser JWT when redirecting to cross origin for page requests', f
expect(resp?.headers.get('location')).toEqual(
'https://accounts.included.katydid-92.lcl.dev/sign-in?redirect_url=https%3A%2F%2Fwww.clerk.com%2Fprotected',
);
- expect(clerkClient.authenticateRequest).toBeCalled();
+ expect(clerkClient().authenticateRequest).toBeCalled();
});
});
diff --git a/packages/nextjs/src/server/authMiddleware.ts b/packages/nextjs/src/server/authMiddleware.ts
index f878d6c31c4..4ab37781d98 100644
--- a/packages/nextjs/src/server/authMiddleware.ts
+++ b/packages/nextjs/src/server/authMiddleware.ts
@@ -135,7 +135,7 @@ const authMiddleware: AuthMiddleware = (...args: unknown[]) => {
const isApiRoute = createApiRoutes(options.apiRoutes);
const defaultAfterAuth = createDefaultAfterAuth(isPublicRoute, isApiRoute, options);
- clerkClient.telemetry.record(
+ clerkClient().telemetry.record(
eventMethodCalled('authMiddleware', {
publicRoutes: Boolean(options.publicRoutes),
ignoredRoutes: Boolean(options.ignoredRoutes),
@@ -184,7 +184,7 @@ const authMiddleware: AuthMiddleware = (...args: unknown[]) => {
return setHeader(beforeAuthRes, constants.Headers.AuthReason, 'before-auth-redirect');
}
- const requestState = await clerkClient.authenticateRequest(
+ const requestState = await clerkClient().authenticateRequest(
clerkRequest,
createAuthenticateRequestOptions(clerkRequest, options),
);
diff --git a/packages/nextjs/src/server/clerkClient.ts b/packages/nextjs/src/server/clerkClient.ts
index 7ef41d9f3c7..eb8006408ec 100644
--- a/packages/nextjs/src/server/clerkClient.ts
+++ b/packages/nextjs/src/server/clerkClient.ts
@@ -48,7 +48,9 @@ const clerkClientSingleton = createClerkClient(clerkClientDefaultOptions);
*/
const clerkClientSingletonProxy = new Proxy(clerkClientSingleton, {
get(target, prop, receiver) {
- deprecated('clerkClient object', 'Use `clerkClient()` as a function instead.');
+ if (Object.getPrototypeOf(target) === Object.getPrototypeOf(clerkClientSingleton)) {
+ deprecated('clerkClient object', 'Use `clerkClient()` as a function instead.');
+ }
return Reflect.get(target, prop, receiver);
},
@@ -83,7 +85,7 @@ const clerkClientForRequest = () => {
});
}
- return clerkClient;
+ return clerkClientSingleton;
};
const clerkClient: ClerkClient & typeof clerkClientForRequest = Object.assign(
From 6d96f08364d8363b6bcdc4d3967fe8bf48a54e06 Mon Sep 17 00:00:00 2001
From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com>
Date: Wed, 26 Jun 2024 13:43:29 -0300
Subject: [PATCH 4/9] Rollback changes on `authMiddleware`
---
.../server/__tests__/authMiddleware.test.ts | 22 +++++++++----------
packages/nextjs/src/server/authMiddleware.ts | 4 ++--
2 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/packages/nextjs/src/server/__tests__/authMiddleware.test.ts b/packages/nextjs/src/server/__tests__/authMiddleware.test.ts
index b474ffea8a0..31deb0b2be6 100644
--- a/packages/nextjs/src/server/__tests__/authMiddleware.test.ts
+++ b/packages/nextjs/src/server/__tests__/authMiddleware.test.ts
@@ -228,7 +228,7 @@ describe('authMiddleware(params)', () => {
})(mockRequest({ url: '/ignored' }), {} as NextFetchEvent);
expect(resp?.status).toEqual(200);
- expect(clerkClient().authenticateRequest).not.toBeCalled();
+ expect(clerkClient.authenticateRequest).not.toBeCalled();
expect(beforeAuthSpy).not.toBeCalled();
expect(afterAuthSpy).not.toBeCalled();
});
@@ -243,7 +243,7 @@ describe('authMiddleware(params)', () => {
})(mockRequest({ url: '/protected' }), {} as NextFetchEvent);
expect(resp?.status).toEqual(200);
- expect(clerkClient().authenticateRequest).toBeCalled();
+ expect(clerkClient.authenticateRequest).toBeCalled();
expect(beforeAuthSpy).toBeCalled();
expect(afterAuthSpy).toBeCalled();
});
@@ -310,7 +310,7 @@ describe('authMiddleware(params)', () => {
expect(resp?.status).toEqual(200);
expect(resp?.headers.get('x-clerk-auth-reason')).toEqual('skip');
- expect(clerkClient().authenticateRequest).not.toBeCalled();
+ expect(clerkClient.authenticateRequest).not.toBeCalled();
expect(afterAuthSpy).not.toBeCalled();
});
@@ -322,7 +322,7 @@ describe('authMiddleware(params)', () => {
})(mockRequest({ url: '/protected' }), {} as NextFetchEvent);
expect(resp?.status).toEqual(200);
- expect(clerkClient().authenticateRequest).toBeCalled();
+ expect(clerkClient.authenticateRequest).toBeCalled();
expect(afterAuthSpy).toBeCalled();
});
@@ -335,7 +335,7 @@ describe('authMiddleware(params)', () => {
expect(resp?.status).toEqual(307);
expect(resp?.headers.get('location')).toEqual('https://www.clerk.com/custom-redirect');
- expect(clerkClient().authenticateRequest).not.toBeCalled();
+ expect(clerkClient.authenticateRequest).not.toBeCalled();
expect(afterAuthSpy).not.toBeCalled();
});
@@ -358,7 +358,7 @@ describe('authMiddleware(params)', () => {
expect(resp?.status).toEqual(200);
expect(resp?.headers.get('x-before-auth-header')).toEqual('before');
expect(resp?.headers.get('x-after-auth-header')).toEqual('after');
- expect(clerkClient().authenticateRequest).toBeCalled();
+ expect(clerkClient.authenticateRequest).toBeCalled();
});
});
@@ -372,7 +372,7 @@ describe('authMiddleware(params)', () => {
expect(resp?.headers.get('location')).toEqual(
'https://accounts.included.katydid-92.lcl.dev/sign-in?redirect_url=https%3A%2F%2Fwww.clerk.com%2Fprotected',
);
- expect(clerkClient().authenticateRequest).toBeCalled();
+ expect(clerkClient.authenticateRequest).toBeCalled();
});
it('uses authenticateRequest result as auth', async () => {
@@ -383,7 +383,7 @@ describe('authMiddleware(params)', () => {
await authMiddleware({ afterAuth: afterAuthSpy })(req, event);
- expect(clerkClient().authenticateRequest).toBeCalled();
+ expect(clerkClient.authenticateRequest).toBeCalled();
expect(afterAuthSpy).toBeCalledWith(
{
userId: null,
@@ -421,7 +421,7 @@ describe('Dev Browser JWT when redirecting to cross origin', function () {
expect(resp?.headers.get('location')).toEqual(
'https://accounts.included.katydid-92.lcl.dev/sign-in?redirect_url=https%3A%2F%2Fwww.clerk.com%2Fprotected',
);
- expect(clerkClient().authenticateRequest).toBeCalled();
+ expect(clerkClient.authenticateRequest).toBeCalled();
});
it('appends the Dev Browser JWT to the search when cookie __clerk_db_jwt exists and location is an Account Portal URL', async () => {
@@ -433,7 +433,7 @@ describe('Dev Browser JWT when redirecting to cross origin', function () {
expect(resp?.headers.get('location')).toEqual(
'https://accounts.included.katydid-92.lcl.dev/sign-in?redirect_url=https%3A%2F%2Fwww.clerk.com%2Fprotected&__clerk_db_jwt=test_jwt',
);
- expect(clerkClient().authenticateRequest).toBeCalled();
+ expect(clerkClient.authenticateRequest).toBeCalled();
});
it('does NOT append the Dev Browser JWT if x-clerk-redirect-to header is not set', async () => {
@@ -443,7 +443,7 @@ describe('Dev Browser JWT when redirecting to cross origin', function () {
expect(resp?.status).toEqual(307);
expect(resp?.headers.get('location')).toEqual('https://google.com/');
- expect(clerkClient().authenticateRequest).toBeCalled();
+ expect(clerkClient.authenticateRequest).toBeCalled();
});
});
diff --git a/packages/nextjs/src/server/authMiddleware.ts b/packages/nextjs/src/server/authMiddleware.ts
index 4ab37781d98..f878d6c31c4 100644
--- a/packages/nextjs/src/server/authMiddleware.ts
+++ b/packages/nextjs/src/server/authMiddleware.ts
@@ -135,7 +135,7 @@ const authMiddleware: AuthMiddleware = (...args: unknown[]) => {
const isApiRoute = createApiRoutes(options.apiRoutes);
const defaultAfterAuth = createDefaultAfterAuth(isPublicRoute, isApiRoute, options);
- clerkClient().telemetry.record(
+ clerkClient.telemetry.record(
eventMethodCalled('authMiddleware', {
publicRoutes: Boolean(options.publicRoutes),
ignoredRoutes: Boolean(options.ignoredRoutes),
@@ -184,7 +184,7 @@ const authMiddleware: AuthMiddleware = (...args: unknown[]) => {
return setHeader(beforeAuthRes, constants.Headers.AuthReason, 'before-auth-redirect');
}
- const requestState = await clerkClient().authenticateRequest(
+ const requestState = await clerkClient.authenticateRequest(
clerkRequest,
createAuthenticateRequestOptions(clerkRequest, options),
);
From b10192749d652bb7b5ee353bbc85ffc47562a926 Mon Sep 17 00:00:00 2001
From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com>
Date: Wed, 26 Jun 2024 14:54:24 -0300
Subject: [PATCH 5/9] Run handshake tests
---
integration/tests/dynamic-keys.test.ts | 63 ++++++++++----------------
integration/tests/handshake.test.ts | 2 +-
2 files changed, 26 insertions(+), 39 deletions(-)
diff --git a/integration/tests/dynamic-keys.test.ts b/integration/tests/dynamic-keys.test.ts
index 1fe831755d1..83d053dd1ac 100644
--- a/integration/tests/dynamic-keys.test.ts
+++ b/integration/tests/dynamic-keys.test.ts
@@ -2,6 +2,7 @@ import { test } from '@playwright/test';
import type { Application } from '../models/application';
import { appConfigs } from '../presets';
+import { createTestUtils } from '../testUtils';
test.describe('dynamic keys @nextjs', () => {
test.describe.configure({ mode: 'parallel' });
@@ -12,48 +13,28 @@ test.describe('dynamic keys @nextjs', () => {
.clone()
.addFile(
'src/middleware.ts',
- () => `import { clerkMiddleware } from '@clerk/nextjs/server';
-import { NextResponse } from "next/server";
+ () => `import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
-export default clerkMiddleware();
+ const isProtectedRoute = createRouteMatcher(['/protected']);
-export const config = {
- matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
-};`,
- )
- .addFile(
- 'src/app/provider.tsx',
- () => `'use client'
-import { ClerkProvider } from "@clerk/nextjs"
+ export default clerkMiddleware((auth, request) => {
+ if (isProtectedRoute(request)) {
+ auth().protect();
+ }
+ }, {
+ signInUrl: '/foobar'
+ });
-export function Provider({ children }: { children: any }) {
- return (
-
- {children}
-
- )
-}`,
+ export const config = {
+ matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
+ };`,
)
.addFile(
- 'src/app/layout.tsx',
- () => `import './globals.css';
-import { Inter } from 'next/font/google';
-import { Provider } from './provider';
-
-const inter = Inter({ subsets: ['latin'] });
-
-export const metadata = {
- title: 'Create Next App',
- description: 'Generated by create next app',
-};
-
-export default function RootLayout({ children }: { children: React.ReactNode }) {
+ 'src/app/protected/page.tsx',
+ () => `
+export default function Page({ children }: { children: React.ReactNode }) {
return (
-
-
- {children}
-
-
+ Protected
);
}
`,
@@ -68,6 +49,12 @@ export default function RootLayout({ children }: { children: React.ReactNode })
await app.teardown();
});
- // test('redirects to `signInUrl` on `auth.protect`')
- // test('uses to `secretKey` for auth signature')
+ test('redirects to `signInUrl` on `auth.protect`', async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+ await u.page.goToStart();
+ await u.po.expect.toBeSignedOut();
+
+ await u.page.goToRelative('/protected');
+ await u.page.waitForURL(/foobar/);
+ });
});
diff --git a/integration/tests/handshake.test.ts b/integration/tests/handshake.test.ts
index 5057145ef21..7d8cd203e08 100644
--- a/integration/tests/handshake.test.ts
+++ b/integration/tests/handshake.test.ts
@@ -8,7 +8,7 @@ import { generateConfig, getJwksFromSecretKey } from '../testUtils/handshake';
const PORT = 4199;
-test.skip('Client handshake @generic', () => {
+test.describe('Client handshake @generic', () => {
test.describe.configure({ mode: 'serial' });
let app: Application;
From a0851a640db3e2c3b399e2696ffd68c88b8f4b6f Mon Sep 17 00:00:00 2001
From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com>
Date: Wed, 26 Jun 2024 18:34:15 -0300
Subject: [PATCH 6/9] Add test for `signInUrl`
---
integration/tests/dynamic-keys.test.ts | 21 +++++++++++++++++----
1 file changed, 17 insertions(+), 4 deletions(-)
diff --git a/integration/tests/dynamic-keys.test.ts b/integration/tests/dynamic-keys.test.ts
index 83d053dd1ac..8ec70b192d2 100644
--- a/integration/tests/dynamic-keys.test.ts
+++ b/integration/tests/dynamic-keys.test.ts
@@ -22,26 +22,36 @@ test.describe('dynamic keys @nextjs', () => {
auth().protect();
}
}, {
+ secretKey: process.env.CLERK_DYNAMIC_SECRET_KEY,
signInUrl: '/foobar'
});
export const config = {
- matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
+ matcher: ['/((?!.*\\\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
};`,
)
.addFile(
'src/app/protected/page.tsx',
- () => `
+ () => `import { SignedIn, SignedOut } from '@clerk/nextjs';
+
export default function Page({ children }: { children: React.ReactNode }) {
return (
- Protected
+
+
+ Signed in
+
+
+
+ Signed out
+
+
);
}
`,
)
.commit();
await app.setup();
- await app.withEnv(appConfigs.envs.withEmailCodes);
+ await app.withEnv(appConfigs.envs.withDynamicKeys);
await app.dev();
});
@@ -51,10 +61,13 @@ export default function Page({ children }: { children: React.ReactNode }) {
test('redirects to `signInUrl` on `auth.protect`', async ({ page, context }) => {
const u = createTestUtils({ app, page, context });
+
await u.page.goToStart();
+
await u.po.expect.toBeSignedOut();
await u.page.goToRelative('/protected');
+
await u.page.waitForURL(/foobar/);
});
});
From aa18e355e8677d019e7169bc719e0241861ee16a Mon Sep 17 00:00:00 2001
From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com>
Date: Wed, 26 Jun 2024 19:23:51 -0300
Subject: [PATCH 7/9] Add test to resolve auth signature
---
integration/presets/envs.ts | 29 +++++++++++-----------
integration/tests/dynamic-keys.test.ts | 33 ++++++++++----------------
2 files changed, 28 insertions(+), 34 deletions(-)
diff --git a/integration/presets/envs.ts b/integration/presets/envs.ts
index 13dad47aa48..8ef8d7e9651 100644
--- a/integration/presets/envs.ts
+++ b/integration/presets/envs.ts
@@ -29,8 +29,7 @@ const withEmailCodes = environmentConfig()
.setEnvVariable('public', 'CLERK_PUBLISHABLE_KEY', envKeys['with-email-codes'].pk)
.setEnvVariable('public', 'CLERK_SIGN_IN_URL', '/sign-in')
.setEnvVariable('public', 'CLERK_SIGN_UP_URL', '/sign-up')
- .setEnvVariable('public', 'CLERK_JS_URL', constants.E2E_APP_CLERK_JS || 'http://localhost:18211/clerk.browser.js')
- .setEnvVariable('private', 'CLERK_ENCRYPTION_KEY', constants.E2E_CLERK_ENCRYPTION_KEY);
+ .setEnvVariable('public', 'CLERK_JS_URL', constants.E2E_APP_CLERK_JS || 'http://localhost:18211/clerk.browser.js');
const withEmailLinks = environmentConfig()
.setId('withEmailLinks')
@@ -39,8 +38,7 @@ const withEmailLinks = environmentConfig()
.setEnvVariable('public', 'CLERK_PUBLISHABLE_KEY', envKeys['with-email-links'].pk)
.setEnvVariable('public', 'CLERK_SIGN_IN_URL', '/sign-in')
.setEnvVariable('public', 'CLERK_SIGN_UP_URL', '/sign-up')
- .setEnvVariable('public', 'CLERK_JS_URL', constants.E2E_APP_CLERK_JS || 'http://localhost:18211/clerk.browser.js')
- .setEnvVariable('private', 'CLERK_ENCRYPTION_KEY', constants.E2E_CLERK_ENCRYPTION_KEY);
+ .setEnvVariable('public', 'CLERK_JS_URL', constants.E2E_APP_CLERK_JS || 'http://localhost:18211/clerk.browser.js');
const withCustomRoles = environmentConfig()
.setId('withCustomRoles')
@@ -51,8 +49,7 @@ const withCustomRoles = environmentConfig()
.setEnvVariable('public', 'CLERK_PUBLISHABLE_KEY', envKeys['with-custom-roles'].pk)
.setEnvVariable('public', 'CLERK_SIGN_IN_URL', '/sign-in')
.setEnvVariable('public', 'CLERK_SIGN_UP_URL', '/sign-up')
- .setEnvVariable('public', 'CLERK_JS_URL', constants.E2E_APP_CLERK_JS || 'http://localhost:18211/clerk.browser.js')
- .setEnvVariable('private', 'CLERK_ENCRYPTION_KEY', constants.CLERK_ENCRYPTION_KEY);
+ .setEnvVariable('public', 'CLERK_JS_URL', constants.E2E_APP_CLERK_JS || 'http://localhost:18211/clerk.browser.js');
const withEmailCodesQuickstart = withEmailCodes
.removeEnvVariable('public', 'CLERK_SIGN_IN_URL')
@@ -63,30 +60,33 @@ const withAPCore1ClerkLatest = environmentConfig()
.setEnvVariable('public', 'CLERK_TELEMETRY_DISABLED', true)
.setEnvVariable('private', 'CLERK_SECRET_KEY', envKeys['with-email-codes'].sk)
.setEnvVariable('public', 'CLERK_PUBLISHABLE_KEY', envKeys['with-email-codes'].pk)
- .setEnvVariable('public', 'CLERK_JS_URL', constants.E2E_APP_CLERK_JS || 'http://localhost:18211/clerk.browser.js')
- .setEnvVariable('private', 'CLERK_ENCRYPTION_KEY', constants.CLERK_ENCRYPTION_KEY);
+ .setEnvVariable('public', 'CLERK_JS_URL', constants.E2E_APP_CLERK_JS || 'http://localhost:18211/clerk.browser.js');
const withAPCore1ClerkV4 = environmentConfig()
.setId('withAPCore1ClerkV4')
.setEnvVariable('public', 'CLERK_TELEMETRY_DISABLED', true)
.setEnvVariable('private', 'CLERK_SECRET_KEY', envKeys['with-email-codes'].sk)
- .setEnvVariable('public', 'CLERK_PUBLISHABLE_KEY', envKeys['with-email-codes'].pk)
- .setEnvVariable('private', 'CLERK_ENCRYPTION_KEY', constants.CLERK_ENCRYPTION_KEY);
+ .setEnvVariable('public', 'CLERK_PUBLISHABLE_KEY', envKeys['with-email-codes'].pk);
const withAPCore2ClerkLatest = environmentConfig()
.setId('withAPCore2ClerkLatest')
.setEnvVariable('public', 'CLERK_TELEMETRY_DISABLED', true)
.setEnvVariable('private', 'CLERK_SECRET_KEY', envKeys['core-2-all-enabled'].sk)
.setEnvVariable('public', 'CLERK_PUBLISHABLE_KEY', envKeys['core-2-all-enabled'].pk)
- .setEnvVariable('public', 'CLERK_JS_URL', constants.E2E_APP_CLERK_JS || 'http://localhost:18211/clerk.browser.js')
- .setEnvVariable('private', 'CLERK_ENCRYPTION_KEY', constants.CLERK_ENCRYPTION_KEY);
+ .setEnvVariable('public', 'CLERK_JS_URL', constants.E2E_APP_CLERK_JS || 'http://localhost:18211/clerk.browser.js');
const withAPCore2ClerkV4 = environmentConfig()
.setId('withAPCore2ClerkV4')
.setEnvVariable('public', 'CLERK_TELEMETRY_DISABLED', true)
.setEnvVariable('private', 'CLERK_SECRET_KEY', envKeys['core-2-all-enabled'].sk)
- .setEnvVariable('public', 'CLERK_PUBLISHABLE_KEY', envKeys['core-2-all-enabled'].pk)
- .setEnvVariable('private', 'CLERK_ENCRYPTION_KEY', constants.CLERK_ENCRYPTION_KEY);
+ .setEnvVariable('public', 'CLERK_PUBLISHABLE_KEY', envKeys['core-2-all-enabled'].pk);
+
+const withDynamicKeys = withEmailCodes
+ .clone()
+ .setId('withDynamicKeys')
+ .setEnvVariable('private', 'CLERK_SECRET_KEY', '')
+ .setEnvVariable('private', 'CLERK_DYNAMIC_SECRET_KEY', envKeys['with-email-codes'].sk)
+ .setEnvVariable('private', 'CLERK_ENCRYPTION_KEY', constants.E2E_CLERK_ENCRYPTION_KEY);
export const envs = {
withEmailCodes,
@@ -97,4 +97,5 @@ export const envs = {
withAPCore1ClerkV4,
withAPCore2ClerkLatest,
withAPCore2ClerkV4,
+ withDynamicKeys,
} as const;
diff --git a/integration/tests/dynamic-keys.test.ts b/integration/tests/dynamic-keys.test.ts
index 8ec70b192d2..f0be5864de2 100644
--- a/integration/tests/dynamic-keys.test.ts
+++ b/integration/tests/dynamic-keys.test.ts
@@ -30,25 +30,6 @@ test.describe('dynamic keys @nextjs', () => {
matcher: ['/((?!.*\\\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
};`,
)
- .addFile(
- 'src/app/protected/page.tsx',
- () => `import { SignedIn, SignedOut } from '@clerk/nextjs';
-
-export default function Page({ children }: { children: React.ReactNode }) {
- return (
-
-
- Signed in
-
-
-
- Signed out
-
-
- );
-}
- `,
- )
.commit();
await app.setup();
await app.withEnv(appConfigs.envs.withDynamicKeys);
@@ -59,7 +40,7 @@ export default function Page({ children }: { children: React.ReactNode }) {
await app.teardown();
});
- test('redirects to `signInUrl` on `auth.protect`', async ({ page, context }) => {
+ test('redirects to `signInUrl` on `auth().protect()`', async ({ page, context }) => {
const u = createTestUtils({ app, page, context });
await u.page.goToStart();
@@ -70,4 +51,16 @@ export default function Page({ children }: { children: React.ReactNode }) {
await u.page.waitForURL(/foobar/);
});
+
+ test('resolves auth signature with `secretKey` on `auth().protect()`', async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+
+ await u.page.goToStart();
+
+ await u.po.expect.toBeSignedOut();
+
+ await u.page.goToRelative('/page-protected');
+
+ await u.page.waitForURL(/foobar/);
+ });
});
From 22c6e4572a27b1f64b111db47e40c973754ad4f7 Mon Sep 17 00:00:00 2001
From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com>
Date: Thu, 27 Jun 2024 07:54:06 -0300
Subject: [PATCH 8/9] Add test to call `clerkClient` on application runtime
---
integration/tests/dynamic-keys.test.ts | 47 ++++++++++++++++++++++++--
1 file changed, 45 insertions(+), 2 deletions(-)
diff --git a/integration/tests/dynamic-keys.test.ts b/integration/tests/dynamic-keys.test.ts
index f0be5864de2..c686527226d 100644
--- a/integration/tests/dynamic-keys.test.ts
+++ b/integration/tests/dynamic-keys.test.ts
@@ -1,12 +1,14 @@
-import { test } from '@playwright/test';
+import { expect, test } from '@playwright/test';
import type { Application } from '../models/application';
import { appConfigs } from '../presets';
+import type { FakeUser } from '../testUtils';
import { createTestUtils } from '../testUtils';
test.describe('dynamic keys @nextjs', () => {
test.describe.configure({ mode: 'parallel' });
let app: Application;
+ let fakeUser: FakeUser;
test.beforeAll(async () => {
app = await appConfigs.next.appRouter
@@ -30,16 +32,41 @@ test.describe('dynamic keys @nextjs', () => {
matcher: ['/((?!.*\\\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
};`,
)
+ .addFile(
+ 'src/app/current-user/page.tsx',
+ () => `import { currentUser } from '@clerk/nextjs/server'
+
+ export default async function Page(){
+ const user = await currentUser()
+
+ return {user ?
{user.firstName}
: User not found
}
+ }
+ `,
+ )
.commit();
+
await app.setup();
+
+ await app.withEnv(appConfigs.envs.withCustomRoles);
+ const m = createTestUtils({ app });
+ fakeUser = m.services.users.createFakeUser();
+ await m.services.users.createBapiUser(fakeUser);
+
await app.withEnv(appConfigs.envs.withDynamicKeys);
await app.dev();
});
test.afterAll(async () => {
+ await fakeUser.deleteIfExists();
await app.teardown();
});
+ test.afterEach(async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+ await u.page.signOut();
+ await u.page.context().clearCookies();
+ });
+
test('redirects to `signInUrl` on `auth().protect()`', async ({ page, context }) => {
const u = createTestUtils({ app, page, context });
@@ -56,11 +83,27 @@ test.describe('dynamic keys @nextjs', () => {
const u = createTestUtils({ app, page, context });
await u.page.goToStart();
-
await u.po.expect.toBeSignedOut();
await u.page.goToRelative('/page-protected');
await u.page.waitForURL(/foobar/);
});
+
+ test('calls `clerkClient` with dynamic keys from application runtime', async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+
+ await u.page.goToStart();
+ await u.po.expect.toBeSignedOut();
+ await u.page.goToRelative('/current-user');
+
+ await expect(u.page.getByText(/User not found/i)).toBeVisible();
+
+ await u.po.signIn.goTo();
+ await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password });
+ await u.po.expect.toBeSignedIn();
+
+ await u.page.goToRelative('/current-user');
+ await expect(u.page.getByText(/User not found/i)).toBeVisible();
+ });
});
From 068938cd47877f30000da7164801c80ec8ac3365 Mon Sep 17 00:00:00 2001
From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com>
Date: Thu, 27 Jun 2024 13:30:49 -0300
Subject: [PATCH 9/9] Add test to call `clerkClient` on middleware runtime
---
integration/tests/dynamic-keys.test.ts | 56 +++++++++++---------------
1 file changed, 24 insertions(+), 32 deletions(-)
diff --git a/integration/tests/dynamic-keys.test.ts b/integration/tests/dynamic-keys.test.ts
index c686527226d..4c08e8656ce 100644
--- a/integration/tests/dynamic-keys.test.ts
+++ b/integration/tests/dynamic-keys.test.ts
@@ -2,27 +2,35 @@ import { expect, test } from '@playwright/test';
import type { Application } from '../models/application';
import { appConfigs } from '../presets';
-import type { FakeUser } from '../testUtils';
import { createTestUtils } from '../testUtils';
test.describe('dynamic keys @nextjs', () => {
test.describe.configure({ mode: 'parallel' });
let app: Application;
- let fakeUser: FakeUser;
test.beforeAll(async () => {
app = await appConfigs.next.appRouter
.clone()
.addFile(
'src/middleware.ts',
- () => `import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
+ () => `import { clerkClient, clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
+ import { NextResponse } from 'next/server'
const isProtectedRoute = createRouteMatcher(['/protected']);
+ const shouldFetchBapi = createRouteMatcher(['/fetch-bapi-from-middleware']);
- export default clerkMiddleware((auth, request) => {
+ export default clerkMiddleware(async (auth, request) => {
if (isProtectedRoute(request)) {
auth().protect();
}
+
+ if (shouldFetchBapi(request)){
+ const count = await clerkClient().users.getCount();
+
+ if (count){
+ return NextResponse.redirect(new URL('/users-count', request.url))
+ }
+ }
}, {
secretKey: process.env.CLERK_DYNAMIC_SECRET_KEY,
signInUrl: '/foobar'
@@ -33,31 +41,24 @@ test.describe('dynamic keys @nextjs', () => {
};`,
)
.addFile(
- 'src/app/current-user/page.tsx',
- () => `import { currentUser } from '@clerk/nextjs/server'
+ 'src/app/users-count/page.tsx',
+ () => `import { clerkClient } from '@clerk/nextjs/server'
export default async function Page(){
- const user = await currentUser()
+ const count = await clerkClient().users.getCount()
- return {user ?
{user.firstName}
: User not found
}
+ return Users count: {count}
}
`,
)
.commit();
await app.setup();
-
- await app.withEnv(appConfigs.envs.withCustomRoles);
- const m = createTestUtils({ app });
- fakeUser = m.services.users.createFakeUser();
- await m.services.users.createBapiUser(fakeUser);
-
await app.withEnv(appConfigs.envs.withDynamicKeys);
await app.dev();
});
test.afterAll(async () => {
- await fakeUser.deleteIfExists();
await app.teardown();
});
@@ -81,29 +82,20 @@ test.describe('dynamic keys @nextjs', () => {
test('resolves auth signature with `secretKey` on `auth().protect()`', async ({ page, context }) => {
const u = createTestUtils({ app, page, context });
-
- await u.page.goToStart();
- await u.po.expect.toBeSignedOut();
-
await u.page.goToRelative('/page-protected');
-
await u.page.waitForURL(/foobar/);
});
test('calls `clerkClient` with dynamic keys from application runtime', async ({ page, context }) => {
const u = createTestUtils({ app, page, context });
+ await u.page.goToRelative('/users-count');
+ await expect(u.page.getByText(/Users count/i)).toBeVisible();
+ });
- await u.page.goToStart();
- await u.po.expect.toBeSignedOut();
- await u.page.goToRelative('/current-user');
-
- await expect(u.page.getByText(/User not found/i)).toBeVisible();
-
- await u.po.signIn.goTo();
- await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password });
- await u.po.expect.toBeSignedIn();
-
- await u.page.goToRelative('/current-user');
- await expect(u.page.getByText(/User not found/i)).toBeVisible();
+ test('calls `clerkClient` with dynamic keys from middleware runtime', async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+ await u.page.goToRelative('/fetch-bapi-from-middleware');
+ await u.page.waitForAppUrl('/users-count');
+ await expect(u.page.getByText(/Users count/i)).toBeVisible();
});
});