Skip to content

Commit

Permalink
Revert "Revert "feat(nextjs): Hint correct middleware location when m…
Browse files Browse the repository at this point in the history
…issing clerkMiddleware (#4979) (#5026)"

This reverts commit 932c8c4.
  • Loading branch information
panteliselef committed Jan 28, 2025
1 parent 932c8c4 commit 19d7256
Show file tree
Hide file tree
Showing 9 changed files with 121 additions and 29 deletions.
5 changes: 0 additions & 5 deletions .changeset/orange-clouds-relax.md

This file was deleted.

10 changes: 1 addition & 9 deletions packages/nextjs/src/app-router/server/ClerkProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { getDynamicAuthData } from '../../server/buildClerkProps';
import type { NextClerkProviderProps } from '../../types';
import { canUseKeyless } from '../../utils/feature-flags';
import { mergeNextClerkPropsWithEnv } from '../../utils/mergeNextClerkPropsWithEnv';
import { onlyTry } from '../../utils/only-try';
import { isNext13 } from '../../utils/sdk-versions';
import { ClientClerkProvider } from '../client/ClerkProvider';
import { deleteKeylessAction } from '../keyless-actions';
Expand All @@ -23,15 +24,6 @@ const getNonceFromCSPHeader = React.cache(async function getNonceFromCSPHeader()
return getScriptNonceFromHeader((await headers()).get('Content-Security-Policy') || '') || '';
});

/** Discards errors thrown by attempted code */
const onlyTry = (cb: () => unknown) => {
try {
cb();
} catch {
// ignore
}
};

export async function ClerkProvider(
props: Without<NextClerkProviderProps, '__unstable_invokeMiddlewareOnAuthStateChange'>,
) {
Expand Down
22 changes: 19 additions & 3 deletions packages/nextjs/src/app-router/server/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ import { constants, createClerkRequest, createRedirect, type RedirectFun } from
import { notFound, redirect } from 'next/navigation';

import { PUBLISHABLE_KEY, SIGN_IN_URL, SIGN_UP_URL } from '../../server/constants';
import { createGetAuth } from '../../server/createGetAuth';
import { createAsyncGetAuth } from '../../server/createGetAuth';
import { authAuthHeaderMissing } from '../../server/errors';
import { getAuthKeyFromRequest, getHeader } from '../../server/headers-utils';
import type { AuthProtect } from '../../server/protect';
import { createProtect } from '../../server/protect';
import { decryptClerkRequestData } from '../../server/utils';
import { isNextWithUnstableServerActions } from '../../utils/sdk-versions';
import { buildRequestLike } from './utils';

/**
Expand All @@ -25,8 +26,10 @@ type Auth = AuthObject & {
*/
redirectToSignIn: RedirectFun<ReturnType<typeof redirect>>;
};

export interface AuthFn {
(): Promise<Auth>;

/**
* `auth` includes a single property, the `protect()` method, which you can use in two ways:
* - to check if a user is authenticated (signed in)
Expand Down Expand Up @@ -60,9 +63,22 @@ export const auth: AuthFn = async () => {
require('server-only');

const request = await buildRequestLike();
const authObject = createGetAuth({

const stepsBasedOnSrcDirectory = async () => {
if (isNextWithUnstableServerActions) {
return [];
}

try {
const isSrcAppDir = await import('../../server/keyless-node.js').then(m => m.hasSrcAppDir());
return [`Your Middleware exists at ./${isSrcAppDir ? 'src/' : ''}middleware.ts`];
} catch {
return [];
}
};
const authObject = await createAsyncGetAuth({
debugLoggerName: 'auth()',
noAuthStatusMessage: authAuthHeaderMissing(),
noAuthStatusMessage: authAuthHeaderMissing('auth', await stepsBasedOnSrcDirectory()),
})(request);

const clerkUrl = getAuthKeyFromRequest(request, 'ClerkUrl');
Expand Down
2 changes: 1 addition & 1 deletion packages/nextjs/src/app-router/server/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export async function buildRequestLike(): Promise<NextRequest> {
}

throw new Error(
`Clerk: auth() and currentUser() are only supported in App Router (/app directory).\nIf you're using /pages, try getAuth() instead.\nOriginal error: ${e}`,
`Clerk: auth(), currentUser() and clerkClient(), are only supported in App Router (/app directory).\nIf you're using /pages, try getAuth() instead.\nOriginal error: ${e}`,
);
}
}
Expand Down
4 changes: 2 additions & 2 deletions packages/nextjs/src/server/__tests__/createGetAuth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import hmacSHA1 from 'crypto-js/hmac-sha1';
import { NextRequest } from 'next/server';
import { describe, expect, it } from 'vitest';

import { createGetAuth, getAuth } from '../createGetAuth';
import { createSyncGetAuth, getAuth } from '../createGetAuth';

const mockSecretKey = 'sk_test_mock';

Expand All @@ -16,7 +16,7 @@ const mockTokenSignature = hmacSHA1(mockToken, 'sk_test_mock').toString();

describe('createGetAuth(opts)', () => {
it('returns a getAuth function', () => {
expect(createGetAuth({ debugLoggerName: 'test', noAuthStatusMessage: 'test' })).toBeInstanceOf(Function);
expect(createSyncGetAuth({ debugLoggerName: 'test', noAuthStatusMessage: 'test' })).toBeInstanceOf(Function);
});
});

Expand Down
46 changes: 40 additions & 6 deletions packages/nextjs/src/server/createGetAuth.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,63 @@
import type { AuthObject } from '@clerk/backend';
import { constants } from '@clerk/backend/internal';
import { isTruthy } from '@clerk/shared/underscore';

import { withLogger } from '../utils/debugLogger';
import { isNextWithUnstableServerActions } from '../utils/sdk-versions';
import { getAuthDataFromRequest } from './data/getAuthDataFromRequest';
import { getAuthAuthHeaderMissing } from './errors';
import { getHeader } from './headers-utils';
import { detectClerkMiddleware, getHeader } from './headers-utils';
import type { RequestLike } from './types';
import { assertAuthStatus } from './utils';

export const createGetAuth = ({
export const createAsyncGetAuth = ({
debugLoggerName,
noAuthStatusMessage,
}: {
debugLoggerName: string;
noAuthStatusMessage: string;
}) =>
withLogger(debugLoggerName, logger => {
return async (req: RequestLike, opts?: { secretKey?: string }) => {
if (isTruthy(getHeader(req, constants.Headers.EnableDebug))) {
logger.enable();
}

if (!detectClerkMiddleware(req)) {
// Keep the same behaviour for versions that may have issues with bundling `node:fs`
if (isNextWithUnstableServerActions) {
assertAuthStatus(req, noAuthStatusMessage);
}

const missConfiguredMiddlewareLocation = await import('./keyless-node.js')
.then(m => m.suggestMiddlewareLocation())
.catch(() => undefined);

if (missConfiguredMiddlewareLocation) {
throw new Error(missConfiguredMiddlewareLocation);
}

// still throw there is no suggested move location
assertAuthStatus(req, noAuthStatusMessage);
}

return getAuthDataFromRequest(req, { ...opts, logger });
};
});

export const createSyncGetAuth = ({
debugLoggerName,
noAuthStatusMessage,
}: {
debugLoggerName: string;
noAuthStatusMessage: string;
}) =>
withLogger(debugLoggerName, logger => {
return (req: RequestLike, opts?: { secretKey?: string }): AuthObject => {
return (req: RequestLike, opts?: { secretKey?: string }) => {
if (isTruthy(getHeader(req, constants.Headers.EnableDebug))) {
logger.enable();
}

assertAuthStatus(req, noAuthStatusMessage);

return getAuthDataFromRequest(req, { ...opts, logger });
};
});
Expand Down Expand Up @@ -107,7 +141,7 @@ export const createGetAuth = ({
* }
* ```
*/
export const getAuth = createGetAuth({
export const getAuth = createSyncGetAuth({
debugLoggerName: 'getAuth()',
noAuthStatusMessage: getAuthAuthHeaderMissing(),
});
4 changes: 2 additions & 2 deletions packages/nextjs/src/server/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ Check if signInUrl is missing from your configuration or if it is not an absolut

export const getAuthAuthHeaderMissing = () => authAuthHeaderMissing('getAuth');

export const authAuthHeaderMissing = (helperName = 'auth') =>
export const authAuthHeaderMissing = (helperName = 'auth', prefixSteps?: string[]) =>
`Clerk: ${helperName}() was called but Clerk can't detect usage of clerkMiddleware(). Please ensure the following:
- clerkMiddleware() is used in your Next.js Middleware.
- ${prefixSteps ? [...prefixSteps, ''].join('\n- ') : ' '}clerkMiddleware() is used in your Next.js Middleware.
- Your Middleware matcher is configured to match this route or page.
- If you are using the src directory, make sure the Middleware file is inside of it.
Expand Down
45 changes: 44 additions & 1 deletion packages/nextjs/src/server/keyless-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,4 +212,47 @@ function removeKeyless() {
unlockFileWriting();
}

export { createOrReadKeyless, removeKeyless };
function hasSrcAppDir() {
const { existsSync } = safeNodeRuntimeFs();
const path = safeNodeRuntimePath();

const projectWithAppSrc = path.join(process.cwd(), 'src', 'app');

return !!existsSync(projectWithAppSrc);
}

function suggestMiddlewareLocation() {
const suggestionMessage = (to?: 'src/', from?: 'src/app/' | 'app/') =>
`Clerk: Move your middleware file to ./${to || ''}middleware.ts. Currently located at ./${from || ''}middleware.ts`;

const { existsSync } = safeNodeRuntimeFs();
const path = safeNodeRuntimePath();

const projectWithAppSrcPath = path.join(process.cwd(), 'src', 'app');
const projectWithAppPath = path.join(process.cwd(), 'app');

if (existsSync(projectWithAppSrcPath)) {
if (existsSync(path.join(projectWithAppSrcPath, 'middleware.ts'))) {
return suggestionMessage('src/', 'src/app/');
}

if (existsSync(path.join(process.cwd(), 'middleware.ts'))) {
return suggestionMessage('src/');
}

// default error
return undefined;
}

if (existsSync(projectWithAppPath)) {
if (existsSync(path.join(projectWithAppPath, 'middleware.ts'))) {
return suggestionMessage(undefined, 'app/');
}
// default error
return undefined;
}

return undefined;
}

export { createOrReadKeyless, removeKeyless, suggestMiddlewareLocation, hasSrcAppDir };
12 changes: 12 additions & 0 deletions packages/nextjs/src/utils/only-try.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Discards errors thrown by attempted code
*/
const onlyTry = (cb: () => unknown) => {
try {
cb();
} catch {
// ignore
}
};

export { onlyTry };

0 comments on commit 19d7256

Please sign in to comment.