diff --git a/.changeset/grumpy-drinks-turn.md b/.changeset/grumpy-drinks-turn.md new file mode 100644 index 00000000000..a47c79336af --- /dev/null +++ b/.changeset/grumpy-drinks-turn.md @@ -0,0 +1,13 @@ +--- +'@clerk/nextjs': major +--- + +Drop support for NextJS v12: v12 was released on 26 Oct 2021. Support for security updates stopped on 21 Nov 2022. + +Drop support for NextJS <13.0.4: Various header-related bugs were introduced with the 12.1.0, 12.2.0, 13.0.1, 13.0.2, 13.0.3 NextJS releases which are now fixed since next@^13.0.4. We will be dropping support for these problematic versions in order to greatly reduce complexity in our codebase. + +Drop support for NextJS < 14.0.3 because of the issues described here: https://github.com/clerk/javascript/issues/1436. + +Deprecate `authMiddleware` in favor of `clerkMiddleware`. For more information, see https://clerk.com/docs/upgrade-guides/v5-introduction + +Move the server-side APIs from `@clerk/nextjs` to the `@clerk/nextjs/server` module: `WebhookEventType`, `WebhookEvent`, `verifyToken`, `redirectToSignIn`, `auth`, `buildClerkProps`, `clerkClient`, `currentUser`, `getAuth`, `redirectToSignUp` and `authMiddleware`. For more information, see https://clerk.com/docs/upgrade-guides/v5-introduction diff --git a/.changeset/tricky-bikes-fix.md b/.changeset/tricky-bikes-fix.md new file mode 100644 index 00000000000..61b8ca0837c --- /dev/null +++ b/.changeset/tricky-bikes-fix.md @@ -0,0 +1,5 @@ +--- +'@clerk/backend': major +--- + +Drop unused SearchParams.AuthStatus constant diff --git a/packages/backend/src/constants.ts b/packages/backend/src/constants.ts index 55bb8897239..0f129877403 100644 --- a/packages/backend/src/constants.ts +++ b/packages/backend/src/constants.ts @@ -43,10 +43,6 @@ const Headers = { SecFetchDest: 'sec-fetch-dest', } as const; -const SearchParams = { - AuthStatus: Headers.AuthStatus, -} as const; - const ContentTypes = { Json: 'application/json', } as const; @@ -55,7 +51,6 @@ export const constants = { Attributes, Cookies, Headers, - SearchParams, ContentTypes, QueryParameters, } as const; diff --git a/packages/nextjs/errors/package.json b/packages/nextjs/errors/package.json new file mode 100644 index 00000000000..11198993274 --- /dev/null +++ b/packages/nextjs/errors/package.json @@ -0,0 +1,5 @@ +{ + "main": "../dist/cjs/errors.js", + "module": "../dist/esm/errors.js", + "types": "../dist/types/errors.d.ts" +} diff --git a/packages/nextjs/package.cjs.json b/packages/nextjs/package.cjs.json index c2e8475f09b..2a6bfb38976 100644 --- a/packages/nextjs/package.cjs.json +++ b/packages/nextjs/package.cjs.json @@ -4,14 +4,6 @@ "#components": { "react-server": "./components.server.js", "default": "./components.client.js" - }, - "#server": { - "react-server": "./server-helpers.server.js", - "edge-light": "./server-helpers.server.js", - "worker": "./server-helpers.server.js", - "node": "./server-helpers.server.js", - "browser": "./server-helpers.client.js", - "default": "./server-helpers.client.js" } } } diff --git a/packages/nextjs/package.esm.json b/packages/nextjs/package.esm.json index c2e8475f09b..2a6bfb38976 100644 --- a/packages/nextjs/package.esm.json +++ b/packages/nextjs/package.esm.json @@ -4,14 +4,6 @@ "#components": { "react-server": "./components.server.js", "default": "./components.client.js" - }, - "#server": { - "react-server": "./server-helpers.server.js", - "edge-light": "./server-helpers.server.js", - "worker": "./server-helpers.server.js", - "node": "./server-helpers.server.js", - "browser": "./server-helpers.client.js", - "default": "./server-helpers.client.js" } } } diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index 36fe82a0a93..582c0ec32a6 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -34,14 +34,18 @@ "types": "./dist/types/server/index.d.ts", "import": "./dist/esm/server/index.js", "require": "./dist/cjs/server/index.js" + }, + "./errors": { + "types": "./dist/types/errors.d.ts", + "import": "./dist/esm/errors.js", + "require": "./dist/cjs/errors.js" } }, - "main": "./dist/cjs/index.js", - "module": "./dist/esm/index.js", "types": "./dist/types/index.d.ts", "files": [ "dist", - "server" + "server", + "errors" ], "scripts": { "build": "npm run clean && tsup", @@ -74,7 +78,7 @@ "typescript": "*" }, "peerDependencies": { - "next": ">=10", + "next": "^13.0.4 || ^14.0.3", "react": ">=18", "react-dom": ">=18" }, diff --git a/packages/nextjs/src/app-router/server/utils.ts b/packages/nextjs/src/app-router/server/utils.ts index 5807878d230..2fcf8c22d0b 100644 --- a/packages/nextjs/src/app-router/server/utils.ts +++ b/packages/nextjs/src/app-router/server/utils.ts @@ -1,12 +1,8 @@ -// import { headers } from 'next/headers'; +import { headers } from 'next/headers'; import { NextRequest } from 'next/server'; export const buildRequestLike = () => { try { - // Dynamically import next/headers, otherwise Next12 apps will break - // because next/headers was introduced in next@13 - // eslint-disable-next-line @typescript-eslint/no-var-requires - const { headers } = require('next/headers'); return new NextRequest('https://placeholder.com', { headers: headers() }); } catch (e: any) { if ( diff --git a/packages/nextjs/src/errors.ts b/packages/nextjs/src/errors.ts new file mode 100644 index 00000000000..6462cd43bc6 --- /dev/null +++ b/packages/nextjs/src/errors.ts @@ -0,0 +1 @@ +export { isClerkAPIResponseError, isEmailLinkError, isKnownError, isMetamaskError } from './client-boundary/hooks'; diff --git a/packages/nextjs/src/index.ts b/packages/nextjs/src/index.ts index ba69ff2efa7..5471744740f 100644 --- a/packages/nextjs/src/index.ts +++ b/packages/nextjs/src/index.ts @@ -37,10 +37,6 @@ export { * If you do, app router will break. */ export { - isClerkAPIResponseError, - isEmailLinkError, - isKnownError, - isMetamaskError, useAuth, useClerk, useEmailLink, @@ -62,31 +58,11 @@ export { */ // @ts-ignore import * as ComponentsModule from '#components'; -/** - * Conditionally export server-side helpers. - * This allows to import server-side helpers from the top-level path. - * We defined the runtime and the type values explicitly, - * because TS will not recognize the subpath import unless the HOST - * application sets moduleResolution to 'NodeNext'. - */ -// @ts-ignore -import * as ServerHelperModule from '#server'; import type { ServerComponentsServerModuleTypes } from './components.server'; -import type { ServerHelpersServerModuleTypes } from './server-helpers.server'; - -export const ClerkProvider = ComponentsModule.ClerkProvider as ServerComponentsServerModuleTypes['ClerkProvider']; -export const SignedIn = ComponentsModule.SignedIn as ServerComponentsServerModuleTypes['SignedIn']; -export const SignedOut = ComponentsModule.SignedOut as ServerComponentsServerModuleTypes['SignedOut']; - -export const Protect = ComponentsModule.Protect; -export const auth = ServerHelperModule.auth as ServerHelpersServerModuleTypes['auth']; -export const currentUser = ServerHelperModule.currentUser as ServerHelpersServerModuleTypes['currentUser']; -// export const getAuth = ServerHelperModule.getAuth as ServerHelpersServerModuleTypes['getAuth']; -export const clerkClient = ServerHelperModule.clerkClient as ServerHelpersServerModuleTypes['clerkClient']; -export const authMiddleware = ServerHelperModule.authMiddleware as ServerHelpersServerModuleTypes['authMiddleware']; -export const redirectToSignIn = - ServerHelperModule.redirectToSignIn as ServerHelpersServerModuleTypes['redirectToSignIn']; -export const redirectToSignUp = - ServerHelperModule.redirectToSignUp as ServerHelpersServerModuleTypes['redirectToSignUp']; +export const ClerkProvider = + ComponentsModule.ClerkProvider as unknown as ServerComponentsServerModuleTypes['ClerkProvider']; +export const SignedIn = ComponentsModule.SignedIn as unknown as ServerComponentsServerModuleTypes['SignedIn']; +export const SignedOut = ComponentsModule.SignedOut as unknown as ServerComponentsServerModuleTypes['SignedOut']; +export const Protect = ComponentsModule.Protect as unknown as ServerComponentsServerModuleTypes['Protect']; diff --git a/packages/nextjs/src/server-helpers.client.ts b/packages/nextjs/src/server-helpers.client.ts deleted file mode 100644 index 86a4c652571..00000000000 --- a/packages/nextjs/src/server-helpers.client.ts +++ /dev/null @@ -1,11 +0,0 @@ -const mockImplementation = (name: string) => { - throw new Error(`${name} can only be used in a server environment.`); -}; - -export const auth = () => mockImplementation('auth()'); -export const currentUser = () => mockImplementation('currentUser()'); -export const authMiddleware = () => mockImplementation('authMiddleware()'); -export const getAuth = () => mockImplementation('getAuth()'); -export const clerkClient = () => mockImplementation('clerkClient()'); -export const redirectToSignIn = () => mockImplementation('redirectToSignIn()'); -export const redirectToSignUp = () => mockImplementation('redirectToSignUp()'); diff --git a/packages/nextjs/src/server-helpers.server.ts b/packages/nextjs/src/server-helpers.server.ts deleted file mode 100644 index c16af6a89d9..00000000000 --- a/packages/nextjs/src/server-helpers.server.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { auth } from './app-router/server/auth'; -import { currentUser } from './app-router/server/currentUser'; -import { authMiddleware } from './server/authMiddleware'; -import { clerkClient } from './server/clerkClient'; -import { getAuth } from './server/getAuth'; -import { redirectToSignIn, redirectToSignUp } from './server/redirect'; - -export { auth, authMiddleware, clerkClient, currentUser, getAuth, redirectToSignIn, redirectToSignUp }; - -export type ServerHelpersServerModuleTypes = { - auth: typeof auth; - currentUser: typeof currentUser; - authMiddleware: typeof authMiddleware; - getAuth: typeof getAuth; - clerkClient: typeof clerkClient; - redirectToSignIn: typeof redirectToSignIn; - redirectToSignUp: typeof redirectToSignUp; -}; diff --git a/packages/nextjs/src/server/__tests__/__snapshots__/exports.test.ts.snap b/packages/nextjs/src/server/__tests__/__snapshots__/exports.test.ts.snap index f14079b5887..17c471ccec7 100644 --- a/packages/nextjs/src/server/__tests__/__snapshots__/exports.test.ts.snap +++ b/packages/nextjs/src/server/__tests__/__snapshots__/exports.test.ts.snap @@ -2,56 +2,15 @@ exports[`/server public exports should not include a breaking change 1`] = ` [ - "AllowlistIdentifier", - "AuthStatus", - "Client", - "DeletedObject", - "Email", - "EmailAddress", - "ExternalAccount", - "IdentificationLink", - "Invitation", - "OauthAccessToken", - "ObjectType", - "Organization", - "OrganizationInvitation", - "OrganizationMembership", - "OrganizationMembershipPublicUserData", - "PhoneNumber", - "RedirectUrl", - "SMSMessage", - "Session", - "SignInToken", - "Token", - "TokenVerificationError", - "TokenVerificationErrorReason", - "User", - "Verification", "auth", "authMiddleware", "buildClerkProps", - "buildRequestUrl", "clerkClient", - "constants", - "createAuthenticateRequest", "createClerkClient", - "createIsomorphicRequest", "currentUser", - "debugRequestState", - "decodeJwt", - "deserialize", "getAuth", - "hasValidSignature", - "makeAuthObjectSerializable", - "prunePrivateMetadata", - "redirect", "redirectToSignIn", "redirectToSignUp", - "sanitizeAuthObject", - "signJwt", - "signedInAuthObject", - "signedOutAuthObject", - "verifyJwt", "verifyToken", ] `; diff --git a/packages/nextjs/src/server/authMiddleware.ts b/packages/nextjs/src/server/authMiddleware.ts index b2a8474a275..77f0d306b85 100644 --- a/packages/nextjs/src/server/authMiddleware.ts +++ b/packages/nextjs/src/server/authMiddleware.ts @@ -132,6 +132,10 @@ export interface AuthMiddleware { (params?: AuthMiddlewareParams): NextMiddleware; } +/** + * @deprecated Use `clerkMiddleware` instead. + * Migration guide: https://clerk.com/docs/upgrade-guides/v5-introduction + */ const authMiddleware: AuthMiddleware = (...args: unknown[]) => { const [params = {}] = args as [AuthMiddlewareParams?]; const { beforeAuth, afterAuth, publicRoutes, ignoredRoutes, apiRoutes, ...options } = params; diff --git a/packages/nextjs/src/server/clerkClient.ts b/packages/nextjs/src/server/clerkClient.ts index fc26200d9a4..f16a757b520 100644 --- a/packages/nextjs/src/server/clerkClient.ts +++ b/packages/nextjs/src/server/clerkClient.ts @@ -29,4 +29,3 @@ const clerkClient = createClerkClient({ }); export { clerkClient }; -export * from '@clerk/backend'; diff --git a/packages/nextjs/src/server/index.ts b/packages/nextjs/src/server/index.ts index d19461d34a8..908b4cbc448 100644 --- a/packages/nextjs/src/server/index.ts +++ b/packages/nextjs/src/server/index.ts @@ -1,7 +1,15 @@ -export * from './clerkClient'; +/** + * Generic exports + */ +export { verifyToken, createClerkClient } from '@clerk/backend'; +export type { WebhookEvent, WebhookEventType } from '@clerk/backend'; +export { clerkClient } from './clerkClient'; + +/** + * NextJS-specific exports + */ export { buildClerkProps, getAuth } from './getAuth'; export { redirectToSignIn, redirectToSignUp } from './redirect'; - export { auth } from '../app-router/server/auth'; export { currentUser } from '../app-router/server/currentUser'; export { authMiddleware } from './authMiddleware'; diff --git a/packages/nextjs/src/server/utils.ts b/packages/nextjs/src/server/utils.ts index 6f244db5963..42ef545f133 100644 --- a/packages/nextjs/src/server/utils.ts +++ b/packages/nextjs/src/server/utils.ts @@ -23,43 +23,16 @@ export function getCustomAttributeFromRequest(req: RequestLike, key: string): st } export function getAuthKeyFromRequest(req: RequestLike, key: AuthKey): string | null | undefined { - return ( - getCustomAttributeFromRequest(req, constants.Attributes[key]) || - getHeader(req, constants.Headers[key]) || - (key === 'AuthStatus' ? getQueryParam(req, constants.SearchParams.AuthStatus) : undefined) - ); + return getCustomAttributeFromRequest(req, constants.Attributes[key]) || getHeader(req, constants.Headers[key]); } -// Tries to extract auth status from the request using several strategies // TODO: Rename Auth status and align the naming across media export function getAuthStatusFromRequest(req: RequestLike): string | null | undefined { return ( - getCustomAttributeFromRequest(req, constants.Attributes.AuthStatus) || - getHeader(req, constants.Headers.AuthStatus) || - getQueryParam(req, constants.SearchParams.AuthStatus) + getCustomAttributeFromRequest(req, constants.Attributes.AuthStatus) || getHeader(req, constants.Headers.AuthStatus) ); } -function getQueryParam(req: RequestLike, name: string): string | null | undefined { - if (isNextRequest(req)) { - return req.nextUrl.searchParams.get(name); - } - - // Check if the request contains a parsed query object - // NextApiRequest does, but the IncomingMessage in the GetServerSidePropsContext case does not - let queryParam: string | null | undefined; - if ('query' in req) { - queryParam = req.query[name] as string | undefined; - } - - // Fall back to query string - if (!queryParam) { - const qs = (req.url || '').split('?')[1]; - queryParam = new URLSearchParams(qs).get(name); - } - return queryParam; -} - export function getHeader(req: RequestLike, name: string): string | null | undefined { if (isNextRequest(req)) { return req.headers.get(name); @@ -124,22 +97,6 @@ export const setRequestHeadersOnNextResponse = ( }); }; -/** - * Test whether the currently installed nextjs version supports overriding the request headers. - * This feature was added in nextjs v13.0.1 - * https://github.com/vercel/next.js/pull/41380 - */ -export const nextJsVersionCanOverrideRequestHeaders = () => { - try { - const headerKey = 'clerkTest'; - const headerKeyInRes = `${MIDDLEWARE_HEADER_PREFIX}-${headerKey}`; - const res = NextResponse.next({ request: { headers: new Headers({ [headerKey]: 'true' }) } }); - return res.headers.has(headerKeyInRes); - } catch (e) { - return false; - } -}; - export const injectSSRStateIntoObject = (obj: O, authObject: T) => { // Serializing the state on dev env is a temp workaround for the following issue: // https://github.com/vercel/next.js/discussions/11209|Next.js @@ -188,23 +145,11 @@ export function decorateRequest( } if (rewriteURL) { - if (nextJsVersionCanOverrideRequestHeaders()) { - // If we detect that the host app is using a nextjs installation that reliably sets the - // request headers, we don't need to fall back to the searchParams strategy. - // In this case, we won't set them at all in order to avoid having them visible in the req.url - setRequestHeadersOnNextResponse(res, req, { - [constants.Headers.AuthStatus]: status, - [constants.Headers.AuthMessage]: message || '', - [constants.Headers.AuthReason]: reason || '', - }); - } else { - res.headers.set(constants.Headers.AuthStatus, status); - res.headers.set(constants.Headers.AuthMessage, message || ''); - res.headers.set(constants.Headers.AuthReason, reason || ''); - rewriteURL.searchParams.set(constants.SearchParams.AuthStatus, status); - rewriteURL.searchParams.set(constants.Headers.AuthMessage, message || ''); - rewriteURL.searchParams.set(constants.Headers.AuthReason, reason || ''); - } + setRequestHeadersOnNextResponse(res, req, { + [constants.Headers.AuthStatus]: status, + [constants.Headers.AuthMessage]: message || '', + [constants.Headers.AuthReason]: reason || '', + }); res.headers.set(nextConstants.Headers.NextRewrite, rewriteURL.href); } diff --git a/packages/nextjs/tsconfig.json b/packages/nextjs/tsconfig.json index e8ef5d4615f..8f20e3722f8 100644 --- a/packages/nextjs/tsconfig.json +++ b/packages/nextjs/tsconfig.json @@ -19,7 +19,8 @@ "skipLibCheck": true, "sourceMap": false, "strict": true, - "target": "ES2020" + "target": "ES2020", + "rootDir": "src" }, "include": ["src"] } diff --git a/playground/nextjs/app/app-dir/client-component.tsx b/playground/nextjs/app/app-dir/client-component.tsx new file mode 100644 index 00000000000..cc286b6ebb1 --- /dev/null +++ b/playground/nextjs/app/app-dir/client-component.tsx @@ -0,0 +1,11 @@ +'use client'; + +import React from 'react'; +import { isClerkAPIResponseError, isEmailLinkError, isKnownError, isMetamaskError } from '@clerk/nextjs/errors'; + +export const ClientComponent = () => { + React.useEffect(() => { + console.log({ isClerkAPIResponseError, isEmailLinkError, isKnownError, isMetamaskError }); + }); + return
Client Component
; +}; diff --git a/playground/nextjs/app/app-dir/page.tsx b/playground/nextjs/app/app-dir/page.tsx index a3c307286c3..208d0ed82fa 100644 --- a/playground/nextjs/app/app-dir/page.tsx +++ b/playground/nextjs/app/app-dir/page.tsx @@ -1,20 +1,11 @@ -import { - auth, - clerkClient, - currentUser, - OrganizationSwitcher, - SignedIn, - SignedOut, - SignIn, - UserButton, -} from '@clerk/nextjs'; +import { OrganizationSwitcher, SignedIn, SignedOut, SignIn, UserButton } from '@clerk/nextjs'; +import { auth, clerkClient, currentUser } from '@clerk/nextjs/server'; import Link from 'next/link'; export default async function Page() { const { userId } = auth(); const currentUser_ = await currentUser(); const user = userId ? await clerkClient.users.getUser(userId) : null; - console.log({ userId, currentUser_, user }); return (
diff --git a/playground/nextjs/pages/session-examples/index.tsx b/playground/nextjs/pages/session-examples/index.tsx index ca392b7c9a5..09827464c4c 100644 --- a/playground/nextjs/pages/session-examples/index.tsx +++ b/playground/nextjs/pages/session-examples/index.tsx @@ -1,4 +1,4 @@ -import { useSession, withSession, WithSession, WithSessionProp } from '@clerk/nextjs'; +import { useSession } from '@clerk/nextjs'; import { PublicUserData } from '@clerk/types'; import type { NextPage } from 'next'; import React from 'react'; @@ -20,36 +20,12 @@ function PublicMetadataWithHook() { return