Skip to content

Commit

Permalink
feat(*): Set PACKAGE_NAME and PACKAGE_VERSION as the user-agent heade…
Browse files Browse the repository at this point in the history
…r in BAPI requests (#2579)
  • Loading branch information
nikosdouvlis authored Jan 12, 2024
1 parent 2a615bf commit c59a2d4
Show file tree
Hide file tree
Showing 31 changed files with 358 additions and 193 deletions.
13 changes: 13 additions & 0 deletions .changeset/fresh-boats-fry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
'gatsby-plugin-clerk': patch
'@clerk/clerk-sdk-node': patch
'@clerk/backend': patch
'@clerk/fastify': patch
'@clerk/nextjs': patch
'@clerk/remix': patch
---

Replace the `Clerk-Backend-SDK` header with `User-Agent` in BAPI requests and update it's value to contain both the package name and the package version of the clerk package
executing the request. Eg request from `@clerk/nextjs` to BAPI with append `User-Agent: @clerk/[email protected]` using the latest version.

Miscellaneous changes: The backend test build changed to use tsup.
2 changes: 1 addition & 1 deletion packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"build:declarations": "tsc -p tsconfig.declarations.json",
"publish:local": "npx yalc push --replace --sig",
"build:lib": "tsup --env.NODE_ENV production",
"build:tests": "tsc -p tsconfig.test.json",
"build:tests": "tsup --config tsup.config.test.ts",
"build:runtime": "rsync -r --include '*/' --include '*.js' --include '*.mjs' --exclude='*' src/runtime dist",
"clean": "rimraf ./dist",
"clean:tests": "rimraf ./tests/dist",
Expand Down
14 changes: 7 additions & 7 deletions packages/backend/src/api/factory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export default (QUnit: QUnit) => {
headers: {
Authorization: 'Bearer deadbeef',
'Content-Type': 'application/json',
'Clerk-Backend-SDK': '@clerk/backend',
'User-Agent': '@clerk/backend@0.0.0-test',
},
}),
);
Expand Down Expand Up @@ -78,7 +78,7 @@ export default (QUnit: QUnit) => {
headers: {
Authorization: 'Bearer deadbeef',
'Content-Type': 'application/json',
'Clerk-Backend-SDK': '@clerk/backend',
'User-Agent': '@clerk/backend@0.0.0-test',
},
}),
);
Expand Down Expand Up @@ -112,7 +112,7 @@ export default (QUnit: QUnit) => {
headers: {
Authorization: 'Bearer deadbeef',
'Content-Type': 'application/json',
'Clerk-Backend-SDK': '@clerk/backend',
'User-Agent': '@clerk/backend@0.0.0-test',
},
}),
);
Expand All @@ -136,7 +136,7 @@ export default (QUnit: QUnit) => {
headers: {
Authorization: 'Bearer deadbeef',
'Content-Type': 'application/json',
'Clerk-Backend-SDK': '@clerk/backend',
'User-Agent': '@clerk/backend@0.0.0-test',
},
body: JSON.stringify({
first_name: 'John',
Expand Down Expand Up @@ -180,7 +180,7 @@ export default (QUnit: QUnit) => {
headers: {
Authorization: 'Bearer deadbeef',
'Content-Type': 'application/json',
'Clerk-Backend-SDK': '@clerk/backend',
'User-Agent': '@clerk/backend@0.0.0-test',
},
}),
);
Expand All @@ -204,7 +204,7 @@ export default (QUnit: QUnit) => {
headers: {
Authorization: 'Bearer deadbeef',
'Content-Type': 'application/json',
'Clerk-Backend-SDK': '@clerk/backend',
'User-Agent': '@clerk/backend@0.0.0-test',
},
}),
);
Expand All @@ -229,7 +229,7 @@ export default (QUnit: QUnit) => {
headers: {
Authorization: 'Bearer deadbeef',
'Content-Type': 'application/json',
'Clerk-Backend-SDK': '@clerk/backend',
'User-Agent': '@clerk/backend@0.0.0-test',
},
}),
);
Expand Down
2 changes: 1 addition & 1 deletion packages/backend/src/api/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export function buildRequest(options: CreateBackendApiOptions) {
// Build headers
const headers: Record<string, any> = {
Authorization: `Bearer ${key}`,
'Clerk-Backend-SDK': userAgent,
'User-Agent': userAgent,
...headerParams,
};

Expand Down
6 changes: 4 additions & 2 deletions packages/backend/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
export const API_URL = 'https://api.clerk.dev';
export const API_VERSION = 'v1';

// TODO: Get information from package.json or define them from ESBuild
export const USER_AGENT = `@clerk/backend`;
export const USER_AGENT = `${PACKAGE_NAME}@${PACKAGE_VERSION}`;
export const MAX_CACHE_LAST_UPDATED_AT_SECONDS = 5 * 60;

const Attributes = {
AuthToken: '__clerkAuthToken',
AuthStatus: '__clerkAuthStatus',
AuthReason: '__clerkAuthReason',
AuthMessage: '__clerkAuthMessage',
Expand All @@ -17,6 +17,7 @@ const Cookies = {
} as const;

const Headers = {
AuthToken: 'x-clerk-auth-token',
AuthStatus: 'x-clerk-auth-status',
AuthReason: 'x-clerk-auth-reason',
AuthMessage: 'x-clerk-auth-message',
Expand All @@ -36,6 +37,7 @@ const Headers = {

const SearchParams = {
AuthStatus: Headers.AuthStatus,
AuthToken: Headers.AuthToken,
} as const;

const ContentTypes = {
Expand Down
8 changes: 4 additions & 4 deletions packages/backend/src/tokens/authObjects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import type { RequestState } from './authStatus';
import type { AuthenticateRequestOptions } from './request';

type AuthObjectDebugData = Partial<AuthenticateRequestOptions & RequestState>;
type CreateAuthObjectDebug = (data?: AuthObjectDebugData) => AuthObjectDebug;
type CreateAuthObjectDebug = (data?: Record<string, unknown>) => AuthObjectDebug;
type AuthObjectDebug = () => unknown;

export type SignedInAuthObjectOptions = {
Expand Down Expand Up @@ -71,9 +71,9 @@ export type AuthObject = SignedInAuthObject | SignedOutAuthObject;
const createDebug: CreateAuthObjectDebug = data => {
return () => {
const res = { ...data } || {};
res.apiKey = (res.apiKey || '').substring(0, 7);
res.secretKey = (res.secretKey || '').substring(0, 7);
res.jwtKey = (res.jwtKey || '').substring(0, 7);
res.apiKey = ((res.apiKey as string) || '').substring(0, 7);
res.secretKey = ((res.secretKey as string) || '').substring(0, 7);
res.jwtKey = ((res.jwtKey as string) || '').substring(0, 7);
return { ...res };
};
};
Expand Down
12 changes: 11 additions & 1 deletion packages/backend/src/tokens/authStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export type SignedInState = {
isInterstitial: false;
isUnknown: false;
toAuth: () => SignedInAuthObject;
token: string;
};

export type SignedOutState = {
Expand All @@ -48,6 +49,7 @@ export type SignedOutState = {
isInterstitial: false;
isUnknown: false;
toAuth: () => SignedOutAuthObject;
token: null;
};

export type InterstitialState = Omit<SignedOutState, 'isInterstitial' | 'status' | 'toAuth'> & {
Expand Down Expand Up @@ -83,7 +85,10 @@ export type AuthReason = AuthErrorReason | TokenVerificationErrorReason;

export type RequestState = SignedInState | SignedOutState | InterstitialState | UnknownState;

export async function signedIn<T>(options: T, sessionClaims: JwtPayload): Promise<SignedInState> {
export async function signedIn<T extends { token: string }>(
options: T,
sessionClaims: JwtPayload,
): Promise<SignedInState> {
const {
apiKey,
secretKey,
Expand All @@ -103,6 +108,7 @@ export async function signedIn<T>(options: T, sessionClaims: JwtPayload): Promis
signUpUrl,
afterSignInUrl,
afterSignUpUrl,
token,
} = options as any;

const { sid: sessionId, org_id: orgId, sub: userId } = sessionClaims;
Expand Down Expand Up @@ -159,6 +165,7 @@ export async function signedIn<T>(options: T, sessionClaims: JwtPayload): Promis
isInterstitial: false,
isUnknown: false,
toAuth: () => authObject,
token,
};
}

Expand Down Expand Up @@ -192,6 +199,7 @@ export function signedOut<T>(options: T, reason: AuthReason, message = ''): Sign
isInterstitial: false,
isUnknown: false,
toAuth: () => signedOutAuthObject({ ...options, status: AuthStatus.SignedOut, reason, message }),
token: null,
};
}

Expand Down Expand Up @@ -224,6 +232,7 @@ export function interstitial<T>(options: T, reason: AuthReason, message = ''): I
isInterstitial: true,
isUnknown: false,
toAuth: () => null,
token: null,
};
}

Expand All @@ -246,5 +255,6 @@ export function unknownState<T>(options: T, reason: AuthReason, message = ''): U
isInterstitial: false,
isUnknown: true,
toAuth: () => null,
token: null,
};
}
4 changes: 2 additions & 2 deletions packages/backend/src/tokens/interstitialRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,13 @@ export const hasPositiveClientUatButCookieIsMissing: InterstitialRule = options
export const hasValidHeaderToken: InterstitialRule = async options => {
const { headerToken } = options as any;
const sessionClaims = await verifyRequestState(options, headerToken);
return await signedIn(options, sessionClaims);
return await signedIn({ ...options, token: headerToken }, sessionClaims);
};

export const hasValidCookieToken: InterstitialRule = async options => {
const { cookieToken, clientUat } = options as any;
const sessionClaims = await verifyRequestState(options, cookieToken);
const state = await signedIn(options, sessionClaims);
const state = await signedIn({ ...options, token: cookieToken }, sessionClaims);

const jwt = state.toAuth().sessionClaims;
const cookieTokenIsOutdated = jwt.iat < Number.parseInt(clientUat);
Expand Down
12 changes: 6 additions & 6 deletions packages/backend/src/tokens/keys.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export default (QUnit: QUnit) => {
headers: {
Authorization: 'Bearer deadbeef',
'Content-Type': 'application/json',
'Clerk-Backend-SDK': '@clerk/backend',
'User-Agent': '@clerk/backend@0.0.0-test',
},
});
assert.propEqual(jwk, mockRsaJwk);
Expand All @@ -78,7 +78,7 @@ export default (QUnit: QUnit) => {
headers: {
Authorization: 'Bearer deadbeef',
'Content-Type': 'application/json',
'Clerk-Backend-SDK': '@clerk/backend',
'User-Agent': '@clerk/backend@0.0.0-test',
},
});
assert.propEqual(jwk, mockRsaJwk);
Expand All @@ -96,7 +96,7 @@ export default (QUnit: QUnit) => {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Clerk-Backend-SDK': '@clerk/backend',
'User-Agent': '@clerk/backend@0.0.0-test',
},
});
assert.propEqual(jwk, mockRsaJwk);
Expand All @@ -116,7 +116,7 @@ export default (QUnit: QUnit) => {
headers: {
Authorization: 'Bearer sk_test_deadbeef',
'Content-Type': 'application/json',
'Clerk-Backend-SDK': '@clerk/backend',
'User-Agent': '@clerk/backend@0.0.0-test',
},
});
assert.propEqual(jwk, mockRsaJwk);
Expand Down Expand Up @@ -203,7 +203,7 @@ export default (QUnit: QUnit) => {
action: 'Contact [email protected]',
});
assert.propContains(err, {
message: `Unable to find a signing key in JWKS that matches the kid='${kid}' of the provided session token. Please make sure that the __session cookie or the HTTP authorization header contain a Clerk-generated session JWT. The following kid are available: ${mockRsaJwkKid}, local`,
message: `Unable to find a signing key in JWKS that matches the kid='${kid}' of the provided session token. Please make sure that the __session cookie or the HTTP authorization header contain a Clerk-generated session JWT. The following kid are available: local, ${mockRsaJwkKid}`,
});
} else {
// This should never be reached. If it does, the suite should fail
Expand All @@ -229,7 +229,7 @@ export default (QUnit: QUnit) => {
action: 'Contact [email protected]',
});
assert.propContains(err, {
message: `Unable to find a signing key in JWKS that matches the kid='${kid}' of the provided session token. Please make sure that the __session cookie or the HTTP authorization header contain a Clerk-generated session JWT. The following kid are available: ${mockRsaJwkKid}, local`,
message: `Unable to find a signing key in JWKS that matches the kid='${kid}' of the provided session token. Please make sure that the __session cookie or the HTTP authorization header contain a Clerk-generated session JWT. The following kid are available: local, ${mockRsaJwkKid}`,
});
} else {
// This should never be reached. If it does, the suite should fail
Expand Down
2 changes: 2 additions & 0 deletions packages/backend/src/tokens/request.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ function assertSignedOut(
domain: '',
message: '',
toAuth: {},
token: null,
...expectedState,
});
}
Expand Down Expand Up @@ -80,6 +81,7 @@ function assertInterstitial(
afterSignUpUrl: '',
domain: '',
toAuth: {},
token: null,
...expectedState,
});
}
Expand Down
4 changes: 2 additions & 2 deletions packages/backend/src/tokens/verify.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export default (QUnit: QUnit) => {
headers: {
Authorization: 'Bearer a-valid-key',
'Content-Type': 'application/json',
'Clerk-Backend-SDK': '@clerk/backend',
'User-Agent': '@clerk/backend@0.0.0-test',
},
});
assert.propEqual(payload, mockJwtPayload);
Expand All @@ -68,7 +68,7 @@ export default (QUnit: QUnit) => {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Clerk-Backend-SDK': '@clerk/backend',
'User-Agent': '@clerk/backend@0.0.0-test',
},
});
assert.propEqual(payload, mockJwtPayload);
Expand Down
25 changes: 25 additions & 0 deletions packages/backend/tsup.config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { defineConfig } from 'tsup';

// @ts-ignore
import { name } from './package.json';

export default defineConfig(overrideOptions => {
const isWatch = !!overrideOptions.watch;

return {
entry: ['./src/**/*.{ts,js}'],
outDir: 'tests/dist/',
define: {
PACKAGE_NAME: `"${name}"`,
// use "test" instead of actual package version to avoid updating the tests
// depending on it (eg userAgent related) on every version bump
PACKAGE_VERSION: `"0.0.0-test"`,
__DEV__: `${isWatch}`,
},
external: ['#crypto', '#fetch'],
clean: true,
minify: false,
tsconfig: 'tsconfig.test.json',
format: 'cjs',
};
});
4 changes: 4 additions & 0 deletions packages/fastify/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
globals: {
PACKAGE_NAME: '@clerk/fastify',
PACKAGE_VERSION: '0.0.0-test',
},
displayName: 'fastify',
injectGlobals: true,
roots: ['<rootDir>/src'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ exports[`clerk initializes clerk with constants 1`] = `
"apiVersion": "v1",
"jwtKey": "",
"secretKey": "TEST_API_KEY",
"userAgent": "@clerk/[email protected]",
},
],
]
Expand Down
1 change: 1 addition & 0 deletions packages/fastify/src/__snapshots__/constants.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ exports[`constants from environment variables 1`] = `
"AuthMessage": "x-clerk-auth-message",
"AuthReason": "x-clerk-auth-reason",
"AuthStatus": "x-clerk-auth-status",
"AuthToken": "x-clerk-auth-token",
"Authorization": "authorization",
"ClerkRedirectTo": "x-clerk-redirect-to",
"CloudFrontForwardedProto": "cloudfront-forwarded-proto",
Expand Down
2 changes: 2 additions & 0 deletions packages/fastify/src/clerkClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ export const clerkClient = createClerkClient({
apiUrl: API_URL,
apiVersion: API_VERSION,
jwtKey: JWT_KEY,
// @ts-ignore - defined by tsup config
userAgent: `${PACKAGE_NAME}@${PACKAGE_VERSION}`,
});
2 changes: 1 addition & 1 deletion packages/gatsby-plugin-clerk/src/ssr/clerkClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const clerkClient = Clerk({
apiUrl: API_URL,
apiVersion: API_VERSION,
// TODO: Fetch version from package.json
userAgent: 'gatsby-plugin-clerk',
userAgent: `${PACKAGE_NAME}@${PACKAGE_VERSION}`,
});

const createClerkClient = Clerk;
Expand Down
1 change: 1 addition & 0 deletions packages/nextjs/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
globals: {
PACKAGE_NAME: '@clerk/nextjs',
PACKAGE_VERSION: '0.0.0-test',
},
displayName: 'nextjs',
Expand Down
Loading

0 comments on commit c59a2d4

Please sign in to comment.