Skip to content

Commit

Permalink
chore(repo): v5 extra cleanups (#2169)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
dimkl committed Nov 22, 2023
1 parent 9955938 commit 4bb5705
Show file tree
Hide file tree
Showing 33 changed files with 232 additions and 193 deletions.
26 changes: 26 additions & 0 deletions .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`
14 changes: 1 addition & 13 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/backend/src/tokens/authStatus.ts
Expand Up @@ -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;
Expand Down
96 changes: 96 additions & 0 deletions 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);
});
});
};
101 changes: 43 additions & 58 deletions 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<AuthenticateRequestOptions, 'apiUrl' | 'apiVersion'>;

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<AuthenticateRequestOptions, 'apiUrl' | 'apiVersion'>) => {
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<LoadInterstitialOptions, 'apiUrl'>) =>
loadInterstitialFromLocal({
...rest,
proxyUrl: runtimeProxyUrl || buildProxyUrl,
publishableKey: runtimePublishableKey || buildtimePublishableKey,
isSatellite: runtimeIsSatellite || buildtimeIsSatellite,
domain: runtimeDomain || buildtimeDomain,
});
const localInterstitial = (options: Omit<LoadInterstitialOptions, 'apiUrl'>) => {
const runTimeOptions = mergePreDefinedOptions(buildTimeOptions, options);
return loadInterstitialFromLocal({ ...options, ...runTimeOptions });
};

// TODO: Replace this function with remotePublicInterstitial
const remotePrivateInterstitial = () => apiClient.interstitial.getInterstitial();
Expand Down
12 changes: 6 additions & 6 deletions 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';
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -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);
}
Expand All @@ -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;
Expand All @@ -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;
Expand Down
6 changes: 3 additions & 3 deletions 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';
Expand Down Expand Up @@ -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`);
}
}
Expand Down
8 changes: 8 additions & 0 deletions packages/backend/src/util/mergePreDefinedOptions.ts
@@ -0,0 +1,8 @@
export function mergePreDefinedOptions<T extends Record<string, any>>(preDefinedOptions: T, options: Partial<T>): T {
return Object.keys(preDefinedOptions).reduce(
(obj: T, key: string) => {
return { ...obj, [key]: options[key] || obj[key] };
},
{ ...preDefinedOptions },
);
}
2 changes: 1 addition & 1 deletion 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';
Expand Down

0 comments on commit 4bb5705

Please sign in to comment.