diff --git a/.changeset/mean-numbers-grin.md b/.changeset/mean-numbers-grin.md new file mode 100644 index 0000000000..4dd2c89c61 --- /dev/null +++ b/.changeset/mean-numbers-grin.md @@ -0,0 +1,5 @@ +--- +'@clerk/backend': minor +--- + +Improve ESM support in `@clerk/backend` for Node by using .mjs for #crypto subpath import \ No newline at end of file diff --git a/packages/backend/package.json b/packages/backend/package.json index 304a374ac8..c0f09ad239 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -17,7 +17,10 @@ "edge-light": "./dist/runtime/browser/crypto.mjs", "worker": "./dist/runtime/browser/crypto.mjs", "browser": "./dist/runtime/browser/crypto.mjs", - "node": "./dist/runtime/node/crypto.js", + "node": { + "require": "./dist/runtime/node/crypto.js", + "import": "./dist/runtime/node/crypto.mjs" + }, "default": "./dist/runtime/browser/crypto.mjs" } }, diff --git a/packages/backend/src/exports.test.ts b/packages/backend/src/__tests__/exports.test.ts similarity index 97% rename from packages/backend/src/exports.test.ts rename to packages/backend/src/__tests__/exports.test.ts index 97bed0993d..fa5233c0d2 100644 --- a/packages/backend/src/exports.test.ts +++ b/packages/backend/src/__tests__/exports.test.ts @@ -1,6 +1,6 @@ import type QUnit from 'qunit'; -import * as publicExports from './index'; +import * as publicExports from '../index'; export default (QUnit: QUnit) => { const { module, test } = QUnit; diff --git a/packages/backend/src/redirections.test.ts b/packages/backend/src/__tests__/redirections.test.ts similarity index 99% rename from packages/backend/src/redirections.test.ts rename to packages/backend/src/__tests__/redirections.test.ts index 0085781c7f..d0054cca6f 100644 --- a/packages/backend/src/redirections.test.ts +++ b/packages/backend/src/__tests__/redirections.test.ts @@ -1,7 +1,7 @@ import type QUnit from 'qunit'; import sinon from 'sinon'; -import { redirect } from './redirections'; +import { redirect } from '../redirections'; export default (QUnit: QUnit) => { const { module, test } = QUnit; diff --git a/packages/backend/src/utils.test.ts b/packages/backend/src/__tests__/utils.test.ts similarity index 98% rename from packages/backend/src/utils.test.ts rename to packages/backend/src/__tests__/utils.test.ts index d76615d66a..de8e53dd28 100644 --- a/packages/backend/src/utils.test.ts +++ b/packages/backend/src/__tests__/utils.test.ts @@ -1,6 +1,6 @@ import type QUnit from 'qunit'; -import { buildOrigin, buildRequestUrl } from './utils'; +import { buildOrigin, buildRequestUrl } from '../utils'; export default (QUnit: QUnit) => { const { module, test } = QUnit; diff --git a/packages/backend/src/api/factory.test.ts b/packages/backend/src/api/__tests__/factory.test.ts similarity index 96% rename from packages/backend/src/api/factory.test.ts rename to packages/backend/src/api/__tests__/factory.test.ts index b5e0612c35..93ff7b29ad 100644 --- a/packages/backend/src/api/factory.test.ts +++ b/packages/backend/src/api/__tests__/factory.test.ts @@ -1,12 +1,20 @@ import type QUnit from 'qunit'; import sinon from 'sinon'; -import emailJson from '../fixtures/responses/email.json'; -import userJson from '../fixtures/responses/user.json'; -import runtime from '../runtime'; -import { assertErrorResponse, assertResponse } from '../util/assertResponse'; -import { jsonError, jsonNotOk, jsonOk, jsonPaginatedOk } from '../util/mockFetch'; -import { createBackendApiClient } from './factory'; +// @ts-ignore +import emailJson from '../../fixtures/email.json'; +// @ts-ignore +import userJson from '../../fixtures/user.json'; +import runtime from '../../runtime'; +import { + assertErrorResponse, + assertResponse, + jsonError, + jsonNotOk, + jsonOk, + jsonPaginatedOk, +} from '../../util/testUtils'; +import { createBackendApiClient } from '../factory'; export default (QUnit: QUnit) => { const { module, test } = QUnit; diff --git a/packages/backend/src/fixtures/responses/email.json b/packages/backend/src/fixtures/email.json similarity index 100% rename from packages/backend/src/fixtures/responses/email.json rename to packages/backend/src/fixtures/email.json diff --git a/packages/backend/src/fixtures/responses/user.json b/packages/backend/src/fixtures/user.json similarity index 100% rename from packages/backend/src/fixtures/responses/user.json rename to packages/backend/src/fixtures/user.json diff --git a/packages/backend/src/runtime/index.ts b/packages/backend/src/runtime.ts similarity index 100% rename from packages/backend/src/runtime/index.ts rename to packages/backend/src/runtime.ts diff --git a/packages/backend/src/runtime/node/crypto.mjs b/packages/backend/src/runtime/node/crypto.mjs new file mode 100644 index 0000000000..d509c88ff7 --- /dev/null +++ b/packages/backend/src/runtime/node/crypto.mjs @@ -0,0 +1 @@ +export { webcrypto } from 'node:crypto'; diff --git a/packages/backend/src/tokens/authObjects.test.ts b/packages/backend/src/tokens/__tests__/authObjects.test.ts similarity index 96% rename from packages/backend/src/tokens/authObjects.test.ts rename to packages/backend/src/tokens/__tests__/authObjects.test.ts index 0f29fbefcc..039276bf06 100644 --- a/packages/backend/src/tokens/authObjects.test.ts +++ b/packages/backend/src/tokens/__tests__/authObjects.test.ts @@ -1,6 +1,6 @@ import type QUnit from 'qunit'; -import { makeAuthObjectSerializable, signedOutAuthObject } from './authObjects'; +import { makeAuthObjectSerializable, signedOutAuthObject } from '../authObjects'; export default (QUnit: QUnit) => { const { module, test } = QUnit; diff --git a/packages/backend/src/tokens/factory.test.ts b/packages/backend/src/tokens/__tests__/factory.test.ts similarity index 96% rename from packages/backend/src/tokens/factory.test.ts rename to packages/backend/src/tokens/__tests__/factory.test.ts index 9183116107..63c25c7958 100644 --- a/packages/backend/src/tokens/factory.test.ts +++ b/packages/backend/src/tokens/__tests__/factory.test.ts @@ -1,7 +1,7 @@ import type QUnit from 'qunit'; -import type { ApiClient } from '../api'; -import { createAuthenticateRequest } from './factory'; +import type { ApiClient } from '../../api'; +import { createAuthenticateRequest } from '../factory'; const TEST_PK = 'pk_test_Y2xlcmsuaW5jbHVkZWQua2F0eWRpZC05Mi5sY2wuZGV2JA'; diff --git a/packages/backend/src/tokens/keys.test.ts b/packages/backend/src/tokens/__tests__/keys.test.ts similarity index 95% rename from packages/backend/src/tokens/keys.test.ts rename to packages/backend/src/tokens/__tests__/keys.test.ts index 682d95d7cf..e6b60afcdc 100644 --- a/packages/backend/src/tokens/keys.test.ts +++ b/packages/backend/src/tokens/__tests__/keys.test.ts @@ -1,11 +1,19 @@ import type QUnit from 'qunit'; import sinon from 'sinon'; -import runtime from '../runtime'; -import { jsonError, jsonOk } from '../util/mockFetch'; -import { TokenVerificationError, TokenVerificationErrorAction, TokenVerificationErrorReason } from './errors'; -import { mockJwks, mockJwtPayload, mockPEMJwk, mockPEMJwtKey, mockPEMKey, mockRsaJwk, mockRsaJwkKid } from './fixtures'; -import { loadClerkJWKFromLocal, loadClerkJWKFromRemote } from './keys'; +import runtime from '../../runtime'; +import { jsonError, jsonOk } from '../../util/testUtils'; +import { TokenVerificationError, TokenVerificationErrorAction, TokenVerificationErrorReason } from '../errors'; +import { + mockJwks, + mockJwtPayload, + mockPEMJwk, + mockPEMJwtKey, + mockPEMKey, + mockRsaJwk, + mockRsaJwkKid, +} from '../fixtures'; +import { loadClerkJWKFromLocal, loadClerkJWKFromRemote } from '../keys'; export default (QUnit: QUnit) => { const { module, test } = QUnit; diff --git a/packages/backend/src/tokens/request.test.ts b/packages/backend/src/tokens/__tests__/request.test.ts similarity index 97% rename from packages/backend/src/tokens/request.test.ts rename to packages/backend/src/tokens/__tests__/request.test.ts index 66dc24b3e3..6a9b48d2a0 100644 --- a/packages/backend/src/tokens/request.test.ts +++ b/packages/backend/src/tokens/__tests__/request.test.ts @@ -1,13 +1,13 @@ import type QUnit from 'qunit'; import sinon from 'sinon'; -import runtime from '../runtime'; -import { jsonOk } from '../util/mockFetch'; -import { AuthErrorReason, type AuthReason, AuthStatus, type RequestState } from './authStatus'; -import { TokenVerificationErrorReason } from './errors'; -import { mockInvalidSignatureJwt, mockJwks, mockJwt, mockJwtPayload, mockMalformedJwt } from './fixtures'; -import type { AuthenticateRequestOptions } from './request'; -import { authenticateRequest, loadOptionsFromHeaders } from './request'; +import runtime from '../../runtime'; +import { jsonOk } from '../../util/testUtils'; +import { AuthErrorReason, type AuthReason, AuthStatus, type RequestState } from '../authStatus'; +import { TokenVerificationErrorReason } from '../errors'; +import { mockInvalidSignatureJwt, mockJwks, mockJwt, mockJwtPayload, mockMalformedJwt } from '../fixtures'; +import type { AuthenticateRequestOptions } from '../request'; +import { authenticateRequest, loadOptionsFromHeaders } from '../request'; function assertSignedOut( assert, @@ -181,12 +181,8 @@ export default (QUnit: QUnit) => { test('returns signed out state if jwk fails to load from remote', async assert => { fakeFetch.onCall(0).returns(jsonOk({})); - const requestState = await authenticateRequest( - mockRequestWithHeaderAuth(), - mockOptions({ - skipJwksCache: false, - }), - ); + + const requestState = await authenticateRequest(mockRequestWithHeaderAuth(), mockOptions()); const errMessage = 'The JWKS endpoint did not contain any signing keys. Contact support@clerk.com. Contact support@clerk.com (reason=jwk-remote-failed-to-load, token-carrier=header)'; diff --git a/packages/backend/src/tokens/verify.test.ts b/packages/backend/src/tokens/__tests__/verify.test.ts similarity index 89% rename from packages/backend/src/tokens/verify.test.ts rename to packages/backend/src/tokens/__tests__/verify.test.ts index c9d89e279a..97f0d10936 100644 --- a/packages/backend/src/tokens/verify.test.ts +++ b/packages/backend/src/tokens/__tests__/verify.test.ts @@ -1,10 +1,10 @@ import type QUnit from 'qunit'; import sinon from 'sinon'; -import runtime from '../runtime'; -import { jsonOk } from '../util/mockFetch'; -import { mockJwks, mockJwt, mockJwtPayload } from './fixtures'; -import { verifyToken } from './verify'; +import runtime from '../../runtime'; +import { jsonOk } from '../../util/testUtils'; +import { mockJwks, mockJwt, mockJwtPayload } from '../fixtures'; +import { verifyToken } from '../verify'; export default (QUnit: QUnit) => { const { module, test } = QUnit; diff --git a/packages/backend/src/tokens/jwt/assertions.test.ts b/packages/backend/src/tokens/jwt/__tests__/assertions.test.ts similarity index 99% rename from packages/backend/src/tokens/jwt/assertions.test.ts rename to packages/backend/src/tokens/jwt/__tests__/assertions.test.ts index 9729620986..d4c27350d1 100644 --- a/packages/backend/src/tokens/jwt/assertions.test.ts +++ b/packages/backend/src/tokens/jwt/__tests__/assertions.test.ts @@ -10,7 +10,7 @@ import { assertHeaderType, assertIssuedAtClaim, assertSubClaim, -} from './assertions'; +} from '../assertions'; export default (QUnit: QUnit) => { const { module, test } = QUnit; diff --git a/packages/backend/src/tokens/jwt/cryptoKeys.test.ts b/packages/backend/src/tokens/jwt/__tests__/cryptoKeys.test.ts similarity index 95% rename from packages/backend/src/tokens/jwt/cryptoKeys.test.ts rename to packages/backend/src/tokens/jwt/__tests__/cryptoKeys.test.ts index ad707d3c1f..3662cd70ef 100644 --- a/packages/backend/src/tokens/jwt/cryptoKeys.test.ts +++ b/packages/backend/src/tokens/jwt/__tests__/cryptoKeys.test.ts @@ -1,7 +1,7 @@ import type QUnit from 'qunit'; -import { pemEncodedPublicKey, pemEncodedSignKey, publicJwks, signingJwks } from '../fixtures'; -import { importKey } from './cryptoKeys'; +import { pemEncodedPublicKey, pemEncodedSignKey, publicJwks, signingJwks } from '../../fixtures'; +import { importKey } from '../cryptoKeys'; export default (QUnit: QUnit) => { const { module, test } = QUnit; diff --git a/packages/backend/src/tokens/jwt/signJwt.test.ts b/packages/backend/src/tokens/jwt/__tests__/signJwt.test.ts similarity index 91% rename from packages/backend/src/tokens/jwt/signJwt.test.ts rename to packages/backend/src/tokens/jwt/__tests__/signJwt.test.ts index 36c7db01f7..22fa071cd3 100644 --- a/packages/backend/src/tokens/jwt/signJwt.test.ts +++ b/packages/backend/src/tokens/jwt/__tests__/signJwt.test.ts @@ -8,9 +8,9 @@ import { pemEncodedSignKey, publicJwks, signingJwks, -} from '../fixtures'; -import { signJwt } from './signJwt'; -import { verifyJwt } from './verifyJwt'; +} from '../../fixtures'; +import { signJwt } from '../signJwt'; +import { verifyJwt } from '../verifyJwt'; export default (QUnit: QUnit) => { const { module, test } = QUnit; diff --git a/packages/backend/src/tokens/jwt/verifyJwt.test.ts b/packages/backend/src/tokens/jwt/__tests__/verifyJwt.test.ts similarity index 97% rename from packages/backend/src/tokens/jwt/verifyJwt.test.ts rename to packages/backend/src/tokens/jwt/__tests__/verifyJwt.test.ts index 8777cb655c..39c9e9007f 100644 --- a/packages/backend/src/tokens/jwt/verifyJwt.test.ts +++ b/packages/backend/src/tokens/jwt/__tests__/verifyJwt.test.ts @@ -10,8 +10,8 @@ import { publicJwks, signedJwt, someOtherPublicKey, -} from '../fixtures'; -import { decodeJwt, hasValidSignature, verifyJwt } from './verifyJwt'; +} from '../../fixtures'; +import { decodeJwt, hasValidSignature, verifyJwt } from '../verifyJwt'; export default (QUnit: QUnit) => { const { module, test } = QUnit; diff --git a/packages/backend/src/tokens/keys.ts b/packages/backend/src/tokens/keys.ts index f6de7713b2..34bbfdcb8d 100644 --- a/packages/backend/src/tokens/keys.ts +++ b/packages/backend/src/tokens/keys.ts @@ -147,7 +147,10 @@ export async function loadClerkJWKFromRemote({ if (!jwk) { const cacheValues = getCacheValues(); - const jwkKeys = cacheValues.map(jwk => jwk.kid).join(', '); + const jwkKeys = cacheValues + .map(jwk => jwk.kid) + .sort() + .join(', '); throw new TokenVerificationError({ action: TokenVerificationErrorAction.ContactSupport, diff --git a/packages/backend/src/util/path.test.ts b/packages/backend/src/util/__tests__/path.test.ts similarity index 88% rename from packages/backend/src/util/path.test.ts rename to packages/backend/src/util/__tests__/path.test.ts index 8ae7d9f95a..ce632d73a8 100644 --- a/packages/backend/src/util/path.test.ts +++ b/packages/backend/src/util/__tests__/path.test.ts @@ -1,6 +1,6 @@ import type QUnit from 'qunit'; -import { joinPaths } from './path'; +import { joinPaths } from '../path'; export default (QUnit: QUnit) => { const { module, test } = QUnit; diff --git a/packages/backend/src/util/request.test.ts b/packages/backend/src/util/__tests__/request.test.ts similarity index 99% rename from packages/backend/src/util/request.test.ts rename to packages/backend/src/util/__tests__/request.test.ts index e24528685e..8ef856e58a 100644 --- a/packages/backend/src/util/request.test.ts +++ b/packages/backend/src/util/__tests__/request.test.ts @@ -1,6 +1,6 @@ import type QUnit from 'qunit'; -import { checkCrossOrigin } from './request'; +import { checkCrossOrigin } from '../request'; export default (QUnit: QUnit) => { const { module, test } = QUnit; diff --git a/packages/backend/src/util/assertResponse.ts b/packages/backend/src/util/assertResponse.ts deleted file mode 100644 index 8cdb760ff2..0000000000 --- a/packages/backend/src/util/assertResponse.ts +++ /dev/null @@ -1,9 +0,0 @@ -type ApiResponse = { data: T | null; errors: null | any[] }; -type SuccessApiResponse = { data: T; errors: null }; -type ErrorApiResponse = { data: null; errors: any[]; clerkTraceId: string; status: number; statusText: string }; -export function assertResponse(assert: Assert, resp: ApiResponse): asserts resp is SuccessApiResponse { - assert.equal(resp.errors, null); -} -export function assertErrorResponse(assert: Assert, resp: ApiResponse): asserts resp is ErrorApiResponse { - assert.notEqual(resp.errors, null); -} diff --git a/packages/backend/src/util/mockFetch.ts b/packages/backend/src/util/testUtils.ts similarity index 76% rename from packages/backend/src/util/mockFetch.ts rename to packages/backend/src/util/testUtils.ts index 8524a845fe..b12f26fcd7 100644 --- a/packages/backend/src/util/mockFetch.ts +++ b/packages/backend/src/util/testUtils.ts @@ -1,5 +1,15 @@ import { constants } from '../constants'; +type ApiResponse = { data: T | null; errors: null | any[] }; +type SuccessApiResponse = { data: T; errors: null }; +type ErrorApiResponse = { data: null; errors: any[]; clerkTraceId: string; status: number; statusText: string }; +export function assertResponse(assert: Assert, resp: ApiResponse): asserts resp is SuccessApiResponse { + assert.equal(resp.errors, null); +} +export function assertErrorResponse(assert: Assert, resp: ApiResponse): asserts resp is ErrorApiResponse { + assert.notEqual(resp.errors, null); +} + export function jsonOk(body: unknown, status = 200) { // Mock response object that satisfies the window.Response interface const mockResponse = { diff --git a/packages/backend/tests/edge-runtime/run.mjs b/packages/backend/tests/edge-runtime/run.mjs index c46f836a4b..108891c2e0 100644 --- a/packages/backend/tests/edge-runtime/run.mjs +++ b/packages/backend/tests/edge-runtime/run.mjs @@ -1,8 +1,9 @@ -import { EdgeRuntime } from 'edge-runtime'; -import { exit } from 'node:process'; import fs from 'node:fs'; -import * as url from 'url'; +import { exit } from 'node:process'; + +import { EdgeRuntime } from 'edge-runtime'; import * as path from 'path'; +import * as url from 'url'; const __dirname = url.fileURLToPath(new URL('.', import.meta.url)); diff --git a/packages/backend/tests/suites.ts b/packages/backend/tests/suites.ts index c83309eedc..40a7dfd84b 100644 --- a/packages/backend/tests/suites.ts +++ b/packages/backend/tests/suites.ts @@ -1,38 +1,39 @@ // Import all suites // TODO: Automate this step using dynamic imports -import factoryTest from './dist/api/factory.test.js'; -import exportsTest from './dist/exports.test.js'; -import redirectTest from './dist/redirections.test.js'; -import authObjectsTest from './dist/tokens/authObjects.test.js'; -import tokenFactoryTest from './dist/tokens/factory.test.js'; -import jwtAssertionsTest from './dist/tokens/jwt/assertions.test.js'; -import cryptoKeysTest from './dist/tokens/jwt/cryptoKeys.test.js'; -import signJwtTest from './dist/tokens/jwt/signJwt.test.js'; -import verifyJwtTest from './dist/tokens/jwt/verifyJwt.test.js'; -import keysTest from './dist/tokens/keys.test.js'; -import requestTest from './dist/tokens/request.test.js'; -import verifyTest from './dist/tokens/verify.test.js'; -import pathTest from './dist/util/path.test.js'; -import utilRequestTest from './dist/util/request.test.js'; -import utilsTest from './dist/utils.test.js'; + +import authObjectsTest from './dist/tokens/__tests__/authObjects.test.js'; +import cryptoKeysTest from './dist/tokens/jwt/__tests__/cryptoKeys.test.js'; +import exportsTest from './dist/__tests__/exports.test.js'; +import factoryTest from './dist/api/__tests__/factory.test.js'; +import jwtAssertionsTest from './dist/tokens/jwt/__tests__/assertions.test.js'; +import keysTest from './dist/tokens/__tests__/keys.test.js'; +import pathTest from './dist/util/__tests__/path.test.js'; +import redirectTest from './dist/__tests__/redirections.test.js'; +import requestTest from './dist/tokens/__tests__/request.test.js'; +import signJwtTest from './dist/tokens/jwt/__tests__/signJwt.test.js'; +import tokenFactoryTest from './dist/tokens/__tests__/factory.test.js'; +import utilRequestTest from './dist/util/__tests__/request.test.js'; +import utilsTest from './dist/__tests__/utils.test.js'; +import verifyJwtTest from './dist/tokens/jwt/__tests__/verifyJwt.test.js'; +import verifyTest from './dist/tokens/__tests__/verify.test.js'; // Add them to the suite array const suites = [ authObjectsTest, + cryptoKeysTest, exportsTest, + factoryTest, jwtAssertionsTest, - requestTest, - utilRequestTest, keysTest, - verifyTest, pathTest, - verifyJwtTest, - signJwtTest, - cryptoKeysTest, - factoryTest, redirectTest, - utilsTest, + requestTest, + signJwtTest, tokenFactoryTest, + utilRequestTest, + utilsTest, + verifyJwtTest, + verifyTest, ]; export default suites; diff --git a/packages/backend/tsconfig.json b/packages/backend/tsconfig.json index 6c79dfa9a5..e93e0bf52d 100644 --- a/packages/backend/tsconfig.json +++ b/packages/backend/tsconfig.json @@ -22,5 +22,5 @@ "isolatedModules": true }, "include": ["src"], - "exclude": ["node_modules", "dist", "/src/runtime/*", "src/**/*.spec.ts", "src/**/*.test.ts", "src/tests"] + "exclude": ["node_modules", "dist", "/src/runtime/*", "src/**/__tests__/*.test.ts", "src/tests"] } diff --git a/packages/backend/tsconfig.test.json b/packages/backend/tsconfig.test.json index c1959296aa..8c600f20dd 100644 --- a/packages/backend/tsconfig.test.json +++ b/packages/backend/tsconfig.test.json @@ -12,6 +12,6 @@ "@clerk/shared/*": ["../shared/dist/*.js"] } }, - "include": ["src/**/*.test.ts"], - "exclude": ["node_modules", "dist", "src/__tests__"] + "include": ["src/**/__tests__"], + "exclude": ["node_modules", "dist"] }