From 51197cbaee97df3ec217ebe0f88e2fb91d3d678b Mon Sep 17 00:00:00 2001 From: Dimitris Klouvas Date: Wed, 22 Nov 2023 12:06:17 +0200 Subject: [PATCH] chore(repo): v5 extra cleanups (#2169) * chore(clerk-expo): Use isomorphicAtob/isomorhpicBtoa to drop base-64 dep * chore(clerk-sdk-node): Add comment and drop todo in interstitial response errors * fix(clerk-react): Rename `HeadlessBrowserClerkConstrutor` / `HeadlessBrowserClerkConstructor` typo * fix(clerk-react): Drop unused import * chore(backend): Refactor merging of props in request/factory * chore(clerk-react,nextjs): Update JSDoc examples with `withServerSideAuth` * chore(shared): Drop `isLegacyFrontendApiKey` * chore(clerk-js): Drop forgotten default exports * chore(clerk-js): Drop node-fetch dependency * chore(backend): Duplicate test * chore(remix): Rename `noSecretKeyOrApiKeyError` to `noSecretKeyError` * chore(clerk-sdk-node): Re-use `isDevelopmentFromApiKey` from `@clerk/shared` * chore(backend): Drop unused `LoadResourcesOptions` export * chore(*): Rename `isDevelopmentFromApiKey` to `isDevelopmentFromSecretKey` * chore(*): Rename `isProductionFromApiKey` to `isProductionFromSecretKey` * chore(repo): Add changeset --- .changeset/red-worms-fetch.md | 26 +++++ package-lock.json | 14 +-- packages/backend/src/tokens/authStatus.ts | 2 +- packages/backend/src/tokens/factory.test.ts | 96 +++++++++++++++++ packages/backend/src/tokens/factory.ts | 101 ++++++++---------- .../backend/src/tokens/interstitialRule.ts | 12 +-- packages/backend/src/tokens/request.ts | 6 +- .../src/util/mergePreDefinedOptions.ts | 8 ++ packages/backend/src/util/shared.ts | 2 +- packages/backend/tests/suites.ts | 4 +- packages/clerk-js/headless/index.d.ts | 3 +- packages/clerk-js/package.json | 1 - .../src/ui.retheme/lazyModules/providers.tsx | 2 +- .../clerk-js/src/ui.retheme/portal/index.tsx | 2 +- .../clerk-js/src/ui/lazyModules/providers.tsx | 2 +- packages/clerk-js/src/ui/portal/index.tsx | 2 +- packages/expo/package.json | 2 - packages/expo/src/polyfills/base64Polyfill.ts | 7 +- packages/nextjs/src/pages/ClerkProvider.tsx | 2 +- packages/nextjs/src/server/authMiddleware.ts | 12 +-- packages/nextjs/src/server/utils.ts | 4 +- packages/react/src/hooks/useAuth.ts | 7 +- packages/react/src/hooks/useSession.ts | 19 ---- packages/react/src/hooks/useUser.ts | 21 ---- packages/react/src/isomorphicClerk.ts | 5 +- packages/react/src/types.ts | 4 +- packages/remix/src/errors.ts | 2 +- packages/remix/src/ssr/authenticateRequest.ts | 12 +-- packages/sdk-node/src/authenticateRequest.ts | 5 +- .../sdk-node/src/clerkExpressRequireAuth.ts | 4 +- packages/sdk-node/src/clerkExpressWithAuth.ts | 4 +- packages/shared/src/__tests__/keys.test.ts | 22 ++-- packages/shared/src/keys.ts | 10 +- 33 files changed, 232 insertions(+), 193 deletions(-) create mode 100644 .changeset/red-worms-fetch.md create mode 100644 packages/backend/src/tokens/factory.test.ts create mode 100644 packages/backend/src/util/mergePreDefinedOptions.ts diff --git a/.changeset/red-worms-fetch.md b/.changeset/red-worms-fetch.md new file mode 100644 index 00000000000..ee950f9bec6 --- /dev/null +++ b/.changeset/red-worms-fetch.md @@ -0,0 +1,26 @@ +--- +'@clerk/clerk-js': major +'@clerk/shared': major +'@clerk/clerk-sdk-node': minor +'@clerk/backend': minor +'@clerk/nextjs': minor +'@clerk/clerk-react': minor +'@clerk/clerk-expo': minor +--- + +Breaking Changes: + +- Drop `isLegacyFrontendApiKey` from `@clerk/shared` +- Drop default exports from `@clerk/clerk-js` + - on headless Clerk type + - on ui and ui.retheme `Portal` +- Use `isProductionFromSecretKey` instead of `isProductionFromApiKey` +- Use `isDevelopmentFromSecretKey` instead of `isDevelopmentFromApiKey` + +Changes: + +- Rename `HeadlessBrowserClerkConstrutor` / `HeadlessBrowserClerkConstructor` (typo) +- Use `isomorphicAtob` / `isomorhpicBtoa` to replace `base-64` in `@clerk/expo` +- Refactor merging build-time and runtime props in `@clerk/backend` clerk client +- Drop `node-fetch` dependency from `@clerk/backend` +- Drop duplicate test in `@clerk/backend` \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 68b5b271a9e..a16e8aa87be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8026,11 +8026,6 @@ "@babel/types": "^7.20.7" } }, - "node_modules/@types/base-64": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, "node_modules/@types/better-sqlite3": { "version": "7.6.4", "dev": true, @@ -10623,10 +10618,6 @@ "version": "1.0.2", "license": "MIT" }, - "node_modules/base-64": { - "version": "1.0.0", - "license": "MIT" - }, "node_modules/base-x": { "version": "3.0.9", "dev": true, @@ -33356,7 +33347,6 @@ "babel-plugin-module-resolver": "^5.0.0", "bundlewatch": "^0.3.3", "eslint-config-custom": "*", - "node-fetch": "^2.6.0", "react-refresh": "^0.14.0", "react-refresh-typescript": "^2.0.5", "semver": "^7.5.2", @@ -33666,12 +33656,10 @@ "@clerk/clerk-js": "5.0.0-alpha-v5.2", "@clerk/clerk-react": "5.0.0-alpha-v5.2", "@clerk/shared": "2.0.0-alpha-v5.2", - "base-64": "1.0.0", "react-native-url-polyfill": "2.0.0" }, "devDependencies": { "@clerk/types": "^4.0.0-alpha-v5.2", - "@types/base-64": "^1.0.0", "@types/node": "^18.17.0", "@types/react": "*", "@types/react-dom": "*", @@ -33978,4 +33966,4 @@ "license": "MIT" } } -} +} \ No newline at end of file diff --git a/packages/backend/src/tokens/authStatus.ts b/packages/backend/src/tokens/authStatus.ts index d87143643f8..401113e61be 100644 --- a/packages/backend/src/tokens/authStatus.ts +++ b/packages/backend/src/tokens/authStatus.ts @@ -81,7 +81,7 @@ export type AuthReason = AuthErrorReason | TokenVerificationErrorReason; export type RequestState = SignedInState | SignedOutState | InterstitialState | UnknownState; -export type LoadResourcesOptions = { +type LoadResourcesOptions = { loadSession?: boolean; loadUser?: boolean; loadOrganization?: boolean; diff --git a/packages/backend/src/tokens/factory.test.ts b/packages/backend/src/tokens/factory.test.ts new file mode 100644 index 00000000000..ced8f12a71d --- /dev/null +++ b/packages/backend/src/tokens/factory.test.ts @@ -0,0 +1,96 @@ +import type QUnit from 'qunit'; + +import type { ApiClient } from '../api'; +import { createAuthenticateRequest } from './factory'; + +export default (QUnit: QUnit) => { + const { module, test } = QUnit; + + module('createAuthenticateRequest({ options, apiClient })', hooks => { + let fakeAuthenticateRequest; + hooks.afterEach(() => { + fakeAuthenticateRequest?.restore(); + }); + + test('fallbacks to build-time options', async assert => { + const buildTimeOptions = { + secretKey: 'sk', + jwtKey: 'jwtKey', + apiUrl: 'apiUrl', + apiVersion: 'apiVersion', + proxyUrl: 'proxyUrl', + publishableKey: 'pk', + isSatellite: false, + domain: 'domain', + audience: 'domain', + }; + + const { authenticateRequest } = createAuthenticateRequest({ + options: buildTimeOptions, + apiClient: {} as ApiClient, + }); + + const requestState = await authenticateRequest({ request: new Request('http://example.com/') }); + assert.propContains(requestState.toAuth()?.debug(), buildTimeOptions); + }); + + test('overrides build-time options with runtime options', async assert => { + const buildTimeOptions = { + secretKey: 'sk', + jwtKey: 'jwtKey', + apiUrl: 'apiUrl', + apiVersion: 'apiVersion', + proxyUrl: 'proxyUrl', + publishableKey: 'pk', + isSatellite: false, + domain: 'domain', + audience: 'domain', + }; + + const { authenticateRequest } = createAuthenticateRequest({ + options: buildTimeOptions, + apiClient: {} as ApiClient, + }); + + const overrides = { + secretKey: 'r-sk', + publishableKey: 'r-pk', + }; + const requestState = await authenticateRequest({ + request: new Request('http://example.com/'), + ...overrides, + }); + assert.propContains(requestState.toAuth()?.debug(), { + ...buildTimeOptions, + ...overrides, + }); + }); + + test('ignore runtime apiUrl and apiVersion options', async assert => { + const buildTimeOptions = { + secretKey: 'sk', + jwtKey: 'jwtKey', + apiUrl: 'apiUrl', + apiVersion: 'apiVersion', + proxyUrl: 'proxyUrl', + publishableKey: 'pk', + isSatellite: false, + domain: 'domain', + audience: 'domain', + }; + + const { authenticateRequest } = createAuthenticateRequest({ + options: buildTimeOptions, + apiClient: {} as ApiClient, + }); + + const requestState = await authenticateRequest({ + request: new Request('http://example.com/'), + // @ts-expect-error is used to check runtime code + apiUrl: 'r-apiUrl', + apiVersion: 'r-apiVersion', + }); + assert.propContains(requestState.toAuth()?.debug(), buildTimeOptions); + }); + }); +}; diff --git a/packages/backend/src/tokens/factory.ts b/packages/backend/src/tokens/factory.ts index 7fed4f84b27..f24821ad634 100644 --- a/packages/backend/src/tokens/factory.ts +++ b/packages/backend/src/tokens/factory.ts @@ -1,80 +1,65 @@ import type { ApiClient } from '../api'; -import { API_URL, API_VERSION } from '../constants'; +import { mergePreDefinedOptions } from '../util/mergePreDefinedOptions'; import type { LoadInterstitialOptions } from './interstitial'; import { loadInterstitialFromLocal } from './interstitial'; import type { AuthenticateRequestOptions } from './request'; import { authenticateRequest as authenticateRequestOriginal, debugRequestState } from './request'; +type RunTimeOptions = Omit; + +type BuildTimeOptions = Partial< + Pick< + AuthenticateRequestOptions, + | 'apiUrl' + | 'apiVersion' + | 'audience' + | 'domain' + | 'isSatellite' + | 'jwtKey' + | 'proxyUrl' + | 'publishableKey' + | 'secretKey' + > +>; + +const defaultOptions = { + secretKey: '', + jwtKey: '', + apiUrl: undefined, + apiVersion: undefined, + proxyUrl: '', + publishableKey: '', + isSatellite: false, + domain: '', + audience: '', +} satisfies BuildTimeOptions; + export type CreateAuthenticateRequestOptions = { - options: Partial< - Pick< - AuthenticateRequestOptions, - | 'audience' - | 'secretKey' - | 'apiUrl' - | 'apiVersion' - | 'publishableKey' - | 'jwtKey' - | 'proxyUrl' - | 'domain' - | 'isSatellite' - > - >; + options: BuildTimeOptions; apiClient: ApiClient; }; export function createAuthenticateRequest(params: CreateAuthenticateRequestOptions) { const { apiClient } = params; - const { - secretKey: buildtimeSecretKey = '', - jwtKey: buildtimeJwtKey = '', - apiUrl = API_URL, - apiVersion = API_VERSION, - proxyUrl: buildProxyUrl = '', - publishableKey: buildtimePublishableKey = '', - isSatellite: buildtimeIsSatellite = false, - domain: buildtimeDomain = '', - audience: buildtimeAudience = '', - } = params.options; + const buildTimeOptions = mergePreDefinedOptions(defaultOptions, params.options); - const authenticateRequest = ({ - secretKey: runtimeSecretKey, - audience: runtimeAudience, - proxyUrl: runtimeProxyUrl, - publishableKey: runtimePublishableKey, - jwtKey: runtimeJwtKey, - isSatellite: runtimeIsSatellite, - domain: runtimeDomain, - ...rest - }: Omit) => { + const authenticateRequest = (options: RunTimeOptions) => { + const { apiUrl, apiVersion } = buildTimeOptions; + const runTimeOptions = mergePreDefinedOptions(buildTimeOptions, options); return authenticateRequestOriginal({ - ...rest, - secretKey: runtimeSecretKey || buildtimeSecretKey, - audience: runtimeAudience || buildtimeAudience, + ...options, + ...runTimeOptions, + // We should add all the omitted props from options here (eg apiUrl / apiVersion) + // to avoid runtime options override them. apiUrl, apiVersion, - proxyUrl: runtimeProxyUrl || buildProxyUrl, - publishableKey: runtimePublishableKey || buildtimePublishableKey, - isSatellite: runtimeIsSatellite || buildtimeIsSatellite, - domain: runtimeDomain || buildtimeDomain, - jwtKey: runtimeJwtKey || buildtimeJwtKey, }); }; - const localInterstitial = ({ - publishableKey: runtimePublishableKey, - proxyUrl: runtimeProxyUrl, - isSatellite: runtimeIsSatellite, - domain: runtimeDomain, - ...rest - }: Omit) => - loadInterstitialFromLocal({ - ...rest, - proxyUrl: runtimeProxyUrl || buildProxyUrl, - publishableKey: runtimePublishableKey || buildtimePublishableKey, - isSatellite: runtimeIsSatellite || buildtimeIsSatellite, - domain: runtimeDomain || buildtimeDomain, - }); + const localInterstitial = (options: Omit) => { + const runTimeOptions = mergePreDefinedOptions(buildTimeOptions, options); + return loadInterstitialFromLocal({ ...options, ...runTimeOptions }); + }; // TODO: Replace this function with remotePublicInterstitial const remotePrivateInterstitial = () => apiClient.interstitial.getInterstitial(); diff --git a/packages/backend/src/tokens/interstitialRule.ts b/packages/backend/src/tokens/interstitialRule.ts index 0593bdc5342..cfc92670081 100644 --- a/packages/backend/src/tokens/interstitialRule.ts +++ b/packages/backend/src/tokens/interstitialRule.ts @@ -1,5 +1,5 @@ import { checkCrossOrigin } from '../util/request'; -import { isDevelopmentFromApiKey, isProductionFromApiKey } from '../util/shared'; +import { isDevelopmentFromSecretKey, isProductionFromSecretKey } from '../util/shared'; import type { AuthStatusOptionsType, RequestState } from './authStatus'; import { AuthErrorReason, interstitial, signedIn, signedOut } from './authStatus'; import { verifyToken } from './verify'; @@ -45,7 +45,7 @@ const isBrowser = (userAgent: string | undefined) => VALID_USER_AGENTS.test(user // automatically treated as signed out. This exception is needed for development, because the any // missing uat throws an interstitial in development. export const nonBrowserRequestInDevRule: InterstitialRule = options => { const { secretKey, userAgent } = options; - if (isDevelopmentFromApiKey(secretKey || '') && !isBrowser(userAgent)) { + if (isDevelopmentFromSecretKey(secretKey || '') && !isBrowser(userAgent)) { return signedOut(options, AuthErrorReason.HeaderMissingNonBrowser); } return undefined; @@ -70,7 +70,7 @@ export const crossOriginRequestWithoutHeader: InterstitialRule = options => { export const isPrimaryInDevAndRedirectsToSatellite: InterstitialRule = options => { const { secretKey = '', isSatellite, searchParams } = options; - const isDev = isDevelopmentFromApiKey(secretKey); + const isDev = isDevelopmentFromSecretKey(secretKey); if (isDev && !isSatellite && shouldRedirectToSatelliteUrl(searchParams)) { return interstitial(options, AuthErrorReason.PrimaryRespondsToSyncing); @@ -80,7 +80,7 @@ export const isPrimaryInDevAndRedirectsToSatellite: InterstitialRule = options = export const potentialFirstLoadInDevWhenUATMissing: InterstitialRule = options => { const { secretKey = '', clientUat } = options; - const res = isDevelopmentFromApiKey(secretKey); + const res = isDevelopmentFromSecretKey(secretKey); if (res && !clientUat) { return interstitial(options, AuthErrorReason.CookieUATMissing); } @@ -96,7 +96,7 @@ export const potentialRequestAfterSignInOrOutFromClerkHostedUiInDev: Interstitia const crossOriginReferrer = referrer && checkCrossOrigin({ originURL: new URL(referrer), host, forwardedHost, forwardedProto }); - if (isDevelopmentFromApiKey(secretKey) && crossOriginReferrer) { + if (isDevelopmentFromSecretKey(secretKey) && crossOriginReferrer) { return interstitial(options, AuthErrorReason.CrossOriginReferrer); } return undefined; @@ -105,7 +105,7 @@ export const potentialRequestAfterSignInOrOutFromClerkHostedUiInDev: Interstitia export const potentialFirstRequestOnProductionEnvironment: InterstitialRule = options => { const { secretKey = '', clientUat, cookieToken } = options; - if (isProductionFromApiKey(secretKey) && !clientUat && !cookieToken) { + if (isProductionFromSecretKey(secretKey) && !clientUat && !cookieToken) { return signedOut(options, AuthErrorReason.CookieAndUATMissing); } return undefined; diff --git a/packages/backend/src/tokens/request.ts b/packages/backend/src/tokens/request.ts index dff88a8d33c..bb61419175d 100644 --- a/packages/backend/src/tokens/request.ts +++ b/packages/backend/src/tokens/request.ts @@ -1,8 +1,8 @@ import { constants } from '../constants'; import { assertValidSecretKey } from '../util/assertValidSecretKey'; import { buildRequest, stripAuthorizationHeader } from '../util/IsomorphicRequest'; -import { isDevelopmentFromApiKey } from '../util/shared'; -import type { AuthStatusOptionsType, LoadResourcesOptions, RequestState } from './authStatus'; +import { isDevelopmentFromSecretKey } from '../util/shared'; +import type { AuthStatusOptionsType, RequestState } from './authStatus'; import { AuthErrorReason, interstitial, signedOut, unknownState } from './authStatus'; import type { TokenCarrier } from './errors'; import { TokenVerificationError, TokenVerificationErrorReason } from './errors'; @@ -32,7 +32,7 @@ export type OptionalVerifyTokenOptions = Partial< export type AuthenticateRequestOptions = AuthStatusOptionsType & OptionalVerifyTokenOptions & { request: Request }; function assertSignInUrlExists(signInUrl: string | undefined, key: string): asserts signInUrl is string { - if (!signInUrl && isDevelopmentFromApiKey(key)) { + if (!signInUrl && isDevelopmentFromSecretKey(key)) { throw new Error(`Missing signInUrl. Pass a signInUrl for dev instances if an app is satellite`); } } diff --git a/packages/backend/src/util/mergePreDefinedOptions.ts b/packages/backend/src/util/mergePreDefinedOptions.ts new file mode 100644 index 00000000000..bdb8562824e --- /dev/null +++ b/packages/backend/src/util/mergePreDefinedOptions.ts @@ -0,0 +1,8 @@ +export function mergePreDefinedOptions>(preDefinedOptions: T, options: Partial): T { + return Object.keys(preDefinedOptions).reduce( + (obj: T, key: string) => { + return { ...obj, [key]: options[key] || obj[key] }; + }, + { ...preDefinedOptions }, + ); +} diff --git a/packages/backend/src/util/shared.ts b/packages/backend/src/util/shared.ts index becfd5f28e7..c873c2822dd 100644 --- a/packages/backend/src/util/shared.ts +++ b/packages/backend/src/util/shared.ts @@ -1,6 +1,6 @@ export { addClerkPrefix, getScriptUrl, getClerkJsMajorVersionOrTag } from '@clerk/shared/url'; export { callWithRetry } from '@clerk/shared/callWithRetry'; -export { isDevelopmentFromApiKey, isProductionFromApiKey, parsePublishableKey } from '@clerk/shared/keys'; +export { isDevelopmentFromSecretKey, isProductionFromSecretKey, parsePublishableKey } from '@clerk/shared/keys'; export { deprecated, deprecatedProperty } from '@clerk/shared/deprecated'; import { buildErrorThrower } from '@clerk/shared/error'; diff --git a/packages/backend/tests/suites.ts b/packages/backend/tests/suites.ts index 5b8bdc36351..c83309eedcc 100644 --- a/packages/backend/tests/suites.ts +++ b/packages/backend/tests/suites.ts @@ -1,10 +1,10 @@ // Import all suites // TODO: Automate this step using dynamic imports -import apiTest from './dist/api/factory.test.js'; 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'; @@ -18,7 +18,6 @@ import utilsTest from './dist/utils.test.js'; // Add them to the suite array const suites = [ - apiTest, authObjectsTest, exportsTest, jwtAssertionsTest, @@ -33,6 +32,7 @@ const suites = [ factoryTest, redirectTest, utilsTest, + tokenFactoryTest, ]; export default suites; diff --git a/packages/clerk-js/headless/index.d.ts b/packages/clerk-js/headless/index.d.ts index 48bee5004fc..b29913ac3f0 100644 --- a/packages/clerk-js/headless/index.d.ts +++ b/packages/clerk-js/headless/index.d.ts @@ -1,4 +1,3 @@ -import { Clerk } from '../dist/types/index.headless'; +export { Clerk } from '../dist/types/index.headless'; -export default Clerk; export * from '../dist/types/index.headless'; diff --git a/packages/clerk-js/package.json b/packages/clerk-js/package.json index b434a39a739..d0216f3c153 100644 --- a/packages/clerk-js/package.json +++ b/packages/clerk-js/package.json @@ -84,7 +84,6 @@ "babel-plugin-module-resolver": "^5.0.0", "bundlewatch": "^0.3.3", "eslint-config-custom": "*", - "node-fetch": "^2.6.0", "react-refresh": "^0.14.0", "react-refresh-typescript": "^2.0.5", "semver": "^7.5.2", diff --git a/packages/clerk-js/src/ui.retheme/lazyModules/providers.tsx b/packages/clerk-js/src/ui.retheme/lazyModules/providers.tsx index a76e0ac432f..730cd62654e 100644 --- a/packages/clerk-js/src/ui.retheme/lazyModules/providers.tsx +++ b/packages/clerk-js/src/ui.retheme/lazyModules/providers.tsx @@ -12,7 +12,7 @@ const OptionsProvider = lazy(() => import('../contexts').then(m => ({ default: m const AppearanceProvider = lazy(() => import('../customizables').then(m => ({ default: m.AppearanceProvider }))); const VirtualRouter = lazy(() => import('../router').then(m => ({ default: m.VirtualRouter }))); const InternalThemeProvider = lazy(() => import('../styledSystem').then(m => ({ default: m.InternalThemeProvider }))); -const Portal = lazy(() => import('./../portal')); +const Portal = lazy(() => import('./../portal').then(m => ({ default: m.Portal }))); const FlowMetadataProvider = lazy(() => import('./../elements').then(m => ({ default: m.FlowMetadataProvider }))); const Modal = lazy(() => import('./../elements').then(m => ({ default: m.Modal }))); diff --git a/packages/clerk-js/src/ui.retheme/portal/index.tsx b/packages/clerk-js/src/ui.retheme/portal/index.tsx index 29755a1b37e..b47c3016df7 100644 --- a/packages/clerk-js/src/ui.retheme/portal/index.tsx +++ b/packages/clerk-js/src/ui.retheme/portal/index.tsx @@ -14,7 +14,7 @@ type PortalProps; -export default class Portal extends React.PureComponent> { +export class Portal extends React.PureComponent> { render(): React.ReactPortal { const { props, component, componentName, node } = this.props; diff --git a/packages/clerk-js/src/ui/lazyModules/providers.tsx b/packages/clerk-js/src/ui/lazyModules/providers.tsx index a76e0ac432f..730cd62654e 100644 --- a/packages/clerk-js/src/ui/lazyModules/providers.tsx +++ b/packages/clerk-js/src/ui/lazyModules/providers.tsx @@ -12,7 +12,7 @@ const OptionsProvider = lazy(() => import('../contexts').then(m => ({ default: m const AppearanceProvider = lazy(() => import('../customizables').then(m => ({ default: m.AppearanceProvider }))); const VirtualRouter = lazy(() => import('../router').then(m => ({ default: m.VirtualRouter }))); const InternalThemeProvider = lazy(() => import('../styledSystem').then(m => ({ default: m.InternalThemeProvider }))); -const Portal = lazy(() => import('./../portal')); +const Portal = lazy(() => import('./../portal').then(m => ({ default: m.Portal }))); const FlowMetadataProvider = lazy(() => import('./../elements').then(m => ({ default: m.FlowMetadataProvider }))); const Modal = lazy(() => import('./../elements').then(m => ({ default: m.Modal }))); diff --git a/packages/clerk-js/src/ui/portal/index.tsx b/packages/clerk-js/src/ui/portal/index.tsx index 29755a1b37e..b47c3016df7 100644 --- a/packages/clerk-js/src/ui/portal/index.tsx +++ b/packages/clerk-js/src/ui/portal/index.tsx @@ -14,7 +14,7 @@ type PortalProps; -export default class Portal extends React.PureComponent> { +export class Portal extends React.PureComponent> { render(): React.ReactPortal { const { props, component, componentName, node } = this.props; diff --git a/packages/expo/package.json b/packages/expo/package.json index 7dd5ad1f30c..f678c7bb405 100644 --- a/packages/expo/package.json +++ b/packages/expo/package.json @@ -42,12 +42,10 @@ "@clerk/clerk-js": "5.0.0-alpha-v5.2", "@clerk/clerk-react": "5.0.0-alpha-v5.2", "@clerk/shared": "2.0.0-alpha-v5.2", - "base-64": "1.0.0", "react-native-url-polyfill": "2.0.0" }, "devDependencies": { "@clerk/types": "^4.0.0-alpha-v5.2", - "@types/base-64": "^1.0.0", "@types/node": "^18.17.0", "@types/react": "*", "@types/react-dom": "*", diff --git a/packages/expo/src/polyfills/base64Polyfill.ts b/packages/expo/src/polyfills/base64Polyfill.ts index 960046fa0da..ddec7d5ade5 100644 --- a/packages/expo/src/polyfills/base64Polyfill.ts +++ b/packages/expo/src/polyfills/base64Polyfill.ts @@ -1,9 +1,10 @@ -import { decode, encode } from 'base-64'; +import { isomorphicAtob } from '@clerk/shared/isomorphicAtob'; +import { isomorphicBtoa } from '@clerk/shared/isomorphicBtoa'; if (!global.btoa) { - global.btoa = encode; + global.btoa = isomorphicBtoa; } if (!global.atob) { - global.atob = decode; + global.atob = isomorphicAtob; } diff --git a/packages/nextjs/src/pages/ClerkProvider.tsx b/packages/nextjs/src/pages/ClerkProvider.tsx index 2aa571720c1..ae574c9fad6 100644 --- a/packages/nextjs/src/pages/ClerkProvider.tsx +++ b/packages/nextjs/src/pages/ClerkProvider.tsx @@ -32,7 +32,7 @@ export function ClerkProvider({ children, ...props }: NextClerkProviderProps): J const navigate = (to: string) => push(to); const mergedProps = mergeNextClerkPropsWithEnv({ ...props, navigate }); - // withServerSideAuth automatically injects __clerk_ssr_state + // ClerkProvider automatically injects __clerk_ssr_state // getAuth returns a user-facing authServerSideProps that hides __clerk_ssr_state // @ts-expect-error initialState is hidden from the types as it's a private prop const initialState = props.authServerSideProps?.__clerk_ssr_state || props.__clerk_ssr_state; diff --git a/packages/nextjs/src/server/authMiddleware.ts b/packages/nextjs/src/server/authMiddleware.ts index faedc4311c5..3a86d060d6b 100644 --- a/packages/nextjs/src/server/authMiddleware.ts +++ b/packages/nextjs/src/server/authMiddleware.ts @@ -1,7 +1,7 @@ import type { AuthObject, RequestState } from '@clerk/backend'; import { buildRequestUrl, constants, TokenVerificationErrorReason } from '@clerk/backend'; import { DEV_BROWSER_JWT_MARKER, setDevBrowserJWTInURL } from '@clerk/shared/devBrowser'; -import { isDevelopmentFromApiKey } from '@clerk/shared/keys'; +import { isDevelopmentFromSecretKey } from '@clerk/shared/keys'; import type { Autocomplete } from '@clerk/types'; import type Link from 'next/link'; import type { NextFetchEvent, NextMiddleware, NextRequest } from 'next/server'; @@ -162,7 +162,7 @@ const authMiddleware: AuthMiddleware = (...args: unknown[]) => { if (isIgnoredRoute(req)) { logger.debug({ isIgnoredRoute: true }); - if (isDevelopmentFromApiKey(options.secretKey || SECRET_KEY) && !params.ignoredRoutes) { + if (isDevelopmentFromSecretKey(options.secretKey || SECRET_KEY) && !params.ignoredRoutes) { console.warn( receivedRequestForIgnoredRoute(req.experimental_clerkUrl.href, JSON.stringify(DEFAULT_CONFIG_MATCHER)), ); @@ -296,7 +296,7 @@ const appendDevBrowserOnCrossOrigin = (req: WithClerkUrl, res: Resp if ( shouldAppendDevBrowser && !!location && - isDevelopmentFromApiKey(opts.secretKey || SECRET_KEY) && + isDevelopmentFromSecretKey(opts.secretKey || SECRET_KEY) && isCrossOrigin(req.experimental_clerkUrl, location) ) { const dbJwt = req.cookies.get(DEV_BROWSER_JWT_MARKER)?.value || ''; @@ -345,7 +345,7 @@ const isRequestMethodIndicatingApiRoute = (req: NextRequest): boolean => { * In development, attempt to detect clock skew based on the requestState. This check should run when requestState.isInterstitial is true. If detected, we throw an error. */ const assertClockSkew = (requestState: RequestState, opts: AuthMiddlewareParams): void => { - if (!isDevelopmentFromApiKey(opts.secretKey || SECRET_KEY)) { + if (!isDevelopmentFromSecretKey(opts.secretKey || SECRET_KEY)) { return; } @@ -363,7 +363,7 @@ const assertInfiniteRedirectionLoop = ( opts: AuthMiddlewareParams, requestState: RequestState, ): NextResponse => { - if (!isDevelopmentFromApiKey(opts.secretKey || SECRET_KEY)) { + if (!isDevelopmentFromSecretKey(opts.secretKey || SECRET_KEY)) { return res; } @@ -403,7 +403,7 @@ const withNormalizedClerkUrl = (req: NextRequest): WithClerkUrl => }; const informAboutProtectedRoute = (path: string, params: AuthMiddlewareParams, isApiRoute: boolean) => { - if (params.debug || isDevelopmentFromApiKey(params.secretKey || SECRET_KEY)) { + if (params.debug || isDevelopmentFromSecretKey(params.secretKey || SECRET_KEY)) { console.warn( informAboutProtectedRouteInfo( path, diff --git a/packages/nextjs/src/server/utils.ts b/packages/nextjs/src/server/utils.ts index 5c03e798815..de745f5e9b9 100644 --- a/packages/nextjs/src/server/utils.ts +++ b/packages/nextjs/src/server/utils.ts @@ -1,7 +1,7 @@ import type { RequestState } from '@clerk/backend'; import { buildRequestUrl, constants } from '@clerk/backend'; import { handleValueOrFn } from '@clerk/shared/handleValueOrFn'; -import { isDevelopmentFromApiKey } from '@clerk/shared/keys'; +import { isDevelopmentFromSecretKey } from '@clerk/shared/keys'; import { isHttpOrHttps } from '@clerk/shared/proxy'; import type { NextRequest } from 'next/server'; import { NextResponse } from 'next/server'; @@ -239,7 +239,7 @@ export const handleMultiDomainAndProxy = (req: NextRequest, opts: WithAuthOption throw new Error(missingDomainAndProxy); } - if (isSatellite && !isHttpOrHttps(signInUrl) && isDevelopmentFromApiKey(SECRET_KEY)) { + if (isSatellite && !isHttpOrHttps(signInUrl) && isDevelopmentFromSecretKey(SECRET_KEY)) { throw new Error(missingSignInUrlInDev); } diff --git a/packages/react/src/hooks/useAuth.ts b/packages/react/src/hooks/useAuth.ts index d5fdf437a68..222aee40a36 100644 --- a/packages/react/src/hooks/useAuth.ts +++ b/packages/react/src/hooks/useAuth.ts @@ -94,8 +94,8 @@ type UseAuth = () => UseAuthReturn; * Once Clerk loads, `isLoaded` will be set to `true`, and you can * safely access the `userId` and `sessionId` variables. * - * For projects using NextJs or Remix, you can have immediate access to this data during SSR - * simply by using the `withServerSideAuth` helper. + * For projects using NextJs or Remix, you can have immediate access to this data during SSR + * simply by using the `ClerkProvider`. * * @example * A simple example: @@ -115,9 +115,6 @@ type UseAuth = () => UseAuthReturn; * Basic example in a NextJs app. This page will be fully rendered during SSR: * * import { useAuth } from '@clerk/nextjs' - * import { withServerSideAuth } from '@clerk/nextjs/api' - * - * export getServerSideProps = withServerSideAuth(); * * export HelloPage = () => { * const { isSignedIn, sessionId, userId } = useAuth(); diff --git a/packages/react/src/hooks/useSession.ts b/packages/react/src/hooks/useSession.ts index 7972ba919df..8025436f22d 100644 --- a/packages/react/src/hooks/useSession.ts +++ b/packages/react/src/hooks/useSession.ts @@ -16,9 +16,6 @@ type UseSession = () => UseSessionReturn; * Once Clerk loads, `isLoaded` will be set to `true`, and you can * safely access `isSignedIn` state and `session`. * - * For projects using NextJs or Remix, you can make this state available during SSR - * simply by using the `withServerSideAuth` helper and setting the `loadSession` flag to `true`. - * * @example * A simple example: * @@ -31,22 +28,6 @@ type UseSession = () => UseSessionReturn; * } * return
{session.updatedAt}
* } - * - * @example - * Basic example in a NextJs app. This page will be fully rendered during SSR: - * - * import { useSession } from '@clerk/nextjs' - * import { withServerSideAuth } from '@clerk/nextjs/api' - * - * export getServerSideProps = withServerSideAuth({ loadSession: true}); - * - * export HelloPage = () => { - * const { isSignedIn, session } = useSession(); - * if(!isSignedIn) { - * return null; - * } - * return
{session.updatedAt}
- * } */ export const useSession: UseSession = () => { const session = useSessionContext(); diff --git a/packages/react/src/hooks/useUser.ts b/packages/react/src/hooks/useUser.ts index b7d1c8f480e..deae35beb88 100644 --- a/packages/react/src/hooks/useUser.ts +++ b/packages/react/src/hooks/useUser.ts @@ -14,10 +14,6 @@ type UseUserReturn = * Once Clerk loads, `isLoaded` will be set to `true`, and you can * safely access `isSignedIn` state and `user`. * - * For projects using NextJs or Remix, you can make this state available during SSR - * simply by using the `withServerSideAuth` helper and setting the `loadUser` flag to `true`. - * - * * @example * A simple example: * @@ -30,23 +26,6 @@ type UseUserReturn = * } * return
Hello, {user.firstName}
* } - * - * @example - * Basic example in a NextJs app. This page will be fully rendered during SSR: - * - * import { useUser } from '@clerk/nextjs' - * import { withServerSideAuth } from '@clerk/nextjs/api' - * - * export getServerSideProps = withServerSideAuth({ loadUser: true}); - * - * export HelloPage = () => { - * const { isSignedIn, user } = useUser(); - * if(!isSignedIn) { - * return null; - * } - * return
Hello, {user.firstName}
- * } - * */ export function useUser(): UseUserReturn { const user = useUserContext(); diff --git a/packages/react/src/isomorphicClerk.ts b/packages/react/src/isomorphicClerk.ts index 93f2ef34618..d26d99b9f16 100644 --- a/packages/react/src/isomorphicClerk.ts +++ b/packages/react/src/isomorphicClerk.ts @@ -12,7 +12,6 @@ import type { HandleOAuthCallbackParams, ListenerCallback, OrganizationListProps, - OrganizationMembershipResource, OrganizationProfileProps, OrganizationResource, OrganizationSwitcherProps, @@ -36,7 +35,7 @@ import type { BrowserClerkConstructor, ClerkProp, HeadlessBrowserClerk, - HeadlessBrowserClerkConstrutor, + HeadlessBrowserClerkConstructor, IsomorphicClerkOptions, } from './types'; import { errorThrower, isConstructor, loadClerkJsScript } from './utils'; @@ -163,7 +162,7 @@ export class IsomorphicClerk { // Set a fixed Clerk version let c: ClerkProp; - if (isConstructor(this.Clerk)) { + if (isConstructor(this.Clerk)) { // Construct a new Clerk object if a constructor is passed c = new this.Clerk(this.#publishableKey, { proxyUrl: this.proxyUrl, diff --git a/packages/react/src/types.ts b/packages/react/src/types.ts index b6800c2c89f..68b31e3ca0e 100644 --- a/packages/react/src/types.ts +++ b/packages/react/src/types.ts @@ -55,7 +55,7 @@ export interface BrowserClerkConstructor { new (publishableKey: string, options?: DomainOrProxyUrl): BrowserClerk; } -export interface HeadlessBrowserClerkConstrutor { +export interface HeadlessBrowserClerkConstructor { new (publishableKey: string, options?: DomainOrProxyUrl): HeadlessBrowserClerk; } @@ -88,7 +88,7 @@ export type ClerkProp = | BrowserClerkConstructor | BrowserClerk | HeadlessBrowserClerk - | HeadlessBrowserClerkConstrutor + | HeadlessBrowserClerkConstructor | undefined | null; diff --git a/packages/remix/src/errors.ts b/packages/remix/src/errors.ts index 499304eef86..2003ef91d01 100644 --- a/packages/remix/src/errors.ts +++ b/packages/remix/src/errors.ts @@ -69,7 +69,7 @@ export const loader: LoaderFunction = args => rootAuthLoader(args, ({ auth }) => }) `); -export const noSecretKeyOrApiKeyError = createErrorMessage(` +export const noSecretKeyError = createErrorMessage(` A secretKey must be provided in order to use SSR and the exports from @clerk/remix/api.'); If your runtime supports environment variables, you can add a CLERK_SECRET_KEY variable to your config. Otherwise, you can pass a secretKey parameter to rootAuthLoader or getAuth. diff --git a/packages/remix/src/ssr/authenticateRequest.ts b/packages/remix/src/ssr/authenticateRequest.ts index b4fe916f4c1..0e1199b0c30 100644 --- a/packages/remix/src/ssr/authenticateRequest.ts +++ b/packages/remix/src/ssr/authenticateRequest.ts @@ -2,15 +2,11 @@ import type { RequestState } from '@clerk/backend'; import { buildRequestUrl, Clerk } from '@clerk/backend'; import { apiUrlFromPublishableKey } from '@clerk/shared/apiUrlFromPublishableKey'; import { handleValueOrFn } from '@clerk/shared/handleValueOrFn'; -import { isDevelopmentFromApiKey } from '@clerk/shared/keys'; +import { isDevelopmentFromSecretKey } from '@clerk/shared/keys'; import { isHttpOrHttps, isProxyUrlRelative } from '@clerk/shared/proxy'; import { isTruthy } from '@clerk/shared/underscore'; -import { - noSecretKeyOrApiKeyError, - satelliteAndMissingProxyUrlAndDomain, - satelliteAndMissingSignInUrl, -} from '../errors'; +import { noSecretKeyError, satelliteAndMissingProxyUrlAndDomain, satelliteAndMissingSignInUrl } from '../errors'; import { getEnvVariable } from '../utils'; import type { LoaderFunctionArgs, RootAuthLoaderOptions } from './types'; @@ -30,7 +26,7 @@ export function authenticateRequest(args: LoaderFunctionArgs, opts: RootAuthLoad const secretKey = opts.secretKey || getEnvVariable('CLERK_SECRET_KEY', context) || ''; if (!secretKey) { - throw new Error(noSecretKeyOrApiKeyError); + throw new Error(noSecretKeyError); } const publishableKey = opts.publishableKey || getEnvVariable('CLERK_PUBLISHABLE_KEY', context) || ''; @@ -73,7 +69,7 @@ export function authenticateRequest(args: LoaderFunctionArgs, opts: RootAuthLoad throw new Error(satelliteAndMissingProxyUrlAndDomain); } - if (isSatellite && !isHttpOrHttps(signInUrl) && isDevelopmentFromApiKey(secretKey)) { + if (isSatellite && !isHttpOrHttps(signInUrl) && isDevelopmentFromSecretKey(secretKey)) { throw new Error(satelliteAndMissingSignInUrl); } diff --git a/packages/sdk-node/src/authenticateRequest.ts b/packages/sdk-node/src/authenticateRequest.ts index ce0eb02bfba..f7b6f6d72a0 100644 --- a/packages/sdk-node/src/authenticateRequest.ts +++ b/packages/sdk-node/src/authenticateRequest.ts @@ -1,6 +1,7 @@ import type { RequestState } from '@clerk/backend'; import { buildRequestUrl, constants, createIsomorphicRequest } from '@clerk/backend'; import { handleValueOrFn } from '@clerk/shared/handleValueOrFn'; +import { isDevelopmentFromSecretKey } from '@clerk/shared/keys'; import { isHttpOrHttps, isProxyUrlRelative, isValidProxyUrl } from '@clerk/shared/proxy'; import type { ServerResponse } from 'http'; @@ -72,7 +73,7 @@ export const authenticateRequest = (opts: AuthenticateRequestParams) => { throw new Error(satelliteAndMissingProxyUrlAndDomain); } - if (isSatellite && !isHttpOrHttps(signInUrl) && isDevelopmentFromApiKey(secretKey || '')) { + if (isSatellite && !isHttpOrHttps(signInUrl) && isDevelopmentFromSecretKey(secretKey || '')) { throw new Error(satelliteAndMissingSignInUrl); } @@ -109,8 +110,6 @@ export const decorateResponseWithObservabilityHeaders = (res: ServerResponse, re requestState.status && res.setHeader(constants.Headers.AuthStatus, encodeURIComponent(requestState.status)); }; -const isDevelopmentFromApiKey = (secretKey: string): boolean => secretKey.startsWith('sk_test_'); - const absoluteProxyUrl = (relativeOrAbsoluteUrl: string, baseUrl: string): string => { if (!relativeOrAbsoluteUrl || !isValidProxyUrl(relativeOrAbsoluteUrl) || !isProxyUrlRelative(relativeOrAbsoluteUrl)) { return relativeOrAbsoluteUrl; diff --git a/packages/sdk-node/src/clerkExpressRequireAuth.ts b/packages/sdk-node/src/clerkExpressRequireAuth.ts index 6bb3b22c677..a71b09a99df 100644 --- a/packages/sdk-node/src/clerkExpressRequireAuth.ts +++ b/packages/sdk-node/src/clerkExpressRequireAuth.ts @@ -39,7 +39,9 @@ export const createClerkExpressRequireAuth = (createOpts: CreateClerkExpressMidd requestState, }); if (interstitial.errors) { - // TODO(@dimkl): return interstitial errors ? + // Temporarily return Unauthenticated instead of the interstitial errors since we don't + // want to expose any internal error (possible errors are http 401, 500 response from BAPI) + // It will be dropped with the removal of fetching remotePrivateInterstitial next(new Error('Unauthenticated')); return; } diff --git a/packages/sdk-node/src/clerkExpressWithAuth.ts b/packages/sdk-node/src/clerkExpressWithAuth.ts index 2407a624f89..08c22297a89 100644 --- a/packages/sdk-node/src/clerkExpressWithAuth.ts +++ b/packages/sdk-node/src/clerkExpressWithAuth.ts @@ -29,7 +29,9 @@ export const createClerkExpressWithAuth = (createOpts: CreateClerkExpressMiddlew requestState, }); if (interstitial.errors) { - // TODO(@dimkl): return interstitial errors ? + // Temporarily return Unauthenticated instead of the interstitial errors since we don't + // want to expose any internal error (possible errors are http 401, 500 response from BAPI) + // It will be dropped with the removal of fetching remotePrivateInterstitial next(new Error('Unauthenticated')); return; } diff --git a/packages/shared/src/__tests__/keys.test.ts b/packages/shared/src/__tests__/keys.test.ts index 6b2198c5c34..598a7c0a0d1 100644 --- a/packages/shared/src/__tests__/keys.test.ts +++ b/packages/shared/src/__tests__/keys.test.ts @@ -1,9 +1,8 @@ import { buildPublishableKey, createDevOrStagingUrlCache, - isDevelopmentFromApiKey, - isLegacyFrontendApiKey, - isProductionFromApiKey, + isDevelopmentFromSecretKey, + isProductionFromSecretKey, isPublishableKey, parsePublishableKey, } from '../keys'; @@ -57,15 +56,6 @@ describe('isPublishableKey(key)', () => { }); }); -describe('isLegacyFrontendApiKey(key)', () => { - it('returns true if the key is a valid legacy frontend Api key', () => { - expect(isLegacyFrontendApiKey('clerk.clerk.com')).toBe(true); - }); - it('returns true if the key is not a valid legacy frontend Api key', () => { - expect(isLegacyFrontendApiKey('pk_live_Y2xlcmsuY2xlcmsuZGV2JA==')).toBe(false); - }); -}); - describe('isDevOrStagingUrl(url)', () => { const { isDevOrStagingUrl } = createDevOrStagingUrlCache(); @@ -93,7 +83,7 @@ describe('isDevOrStagingUrl(url)', () => { }); }); -describe('isDevelopmentFromApiKey(key)', () => { +describe('isDevelopmentFromSecretKey(key)', () => { const cases: Array<[string, boolean]> = [ ['sk_live_Y2xlcmsuY2xlcmsuZGV2JA==', false], ['sk_test_Y2xlcmsuY2xlcmsuZGV2JA==', true], @@ -102,12 +92,12 @@ describe('isDevelopmentFromApiKey(key)', () => { ]; test.each(cases)('given %p as a publishable key string, returns %p', (publishableKeyStr, expected) => { - const result = isDevelopmentFromApiKey(publishableKeyStr); + const result = isDevelopmentFromSecretKey(publishableKeyStr); expect(result).toEqual(expected); }); }); -describe('isProductionFromApiKey(key)', () => { +describe('isProductionFromSecretKey(key)', () => { const cases: Array<[string, boolean]> = [ ['sk_live_Y2xlcmsuY2xlcmsuZGV2JA==', true], ['sk_test_Y2xlcmsuY2xlcmsuZGV2JA==', false], @@ -116,7 +106,7 @@ describe('isProductionFromApiKey(key)', () => { ]; test.each(cases)('given %p as a publishable key string, returns %p', (publishableKeyStr, expected) => { - const result = isProductionFromApiKey(publishableKeyStr); + const result = isProductionFromSecretKey(publishableKeyStr); expect(result).toEqual(expected); }); }); diff --git a/packages/shared/src/keys.ts b/packages/shared/src/keys.ts index 41016991ee3..9d8c056e3ed 100644 --- a/packages/shared/src/keys.ts +++ b/packages/shared/src/keys.ts @@ -50,12 +50,6 @@ export function isPublishableKey(key: string) { return hasValidPrefix && hasValidFrontendApiPostfix; } -export function isLegacyFrontendApiKey(key: string) { - key = key || ''; - - return key.startsWith('clerk.'); -} - export function createDevOrStagingUrlCache() { const devOrStagingUrlCache = new Map(); @@ -76,10 +70,10 @@ export function createDevOrStagingUrlCache() { }; } -export function isDevelopmentFromApiKey(apiKey: string): boolean { +export function isDevelopmentFromSecretKey(apiKey: string): boolean { return apiKey.startsWith('test_') || apiKey.startsWith('sk_test_'); } -export function isProductionFromApiKey(apiKey: string): boolean { +export function isProductionFromSecretKey(apiKey: string): boolean { return apiKey.startsWith('live_') || apiKey.startsWith('sk_live_'); }