Skip to content

Commit afb5923

Browse files
committed
allow custom machine secret key per method
1 parent 2b2ce8e commit afb5923

File tree

4 files changed

+82
-52
lines changed

4 files changed

+82
-52
lines changed

packages/backend/src/api/__tests__/MachineTokenApi.test.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,29 @@ describe('MachineTokenAPI', () => {
6060
http.post(
6161
'https://api.clerk.test/m2m_tokens',
6262
validateHeaders(() => {
63-
return HttpResponse.json(mockM2MToken);
63+
return HttpResponse.json(
64+
{
65+
errors: [
66+
{
67+
message:
68+
'The provided Machine Secret Key is invalid. Make sure that your Machine Secret Key is correct.',
69+
code: 'machine_secret_key_invalid',
70+
},
71+
],
72+
},
73+
{ status: 401 },
74+
);
6475
}),
6576
),
6677
);
6778

68-
const response = await apiClient.machineTokens.create().catch(err => err);
79+
const errResponse = await apiClient.machineTokens.create().catch(err => err);
6980

70-
expect(response.message).toBe('Missing Clerk Machine Secret Key.');
81+
expect(errResponse.status).toBe(401);
82+
expect(errResponse.errors[0].code).toBe('machine_secret_key_invalid');
83+
expect(errResponse.errors[0].message).toBe(
84+
'The provided Machine Secret Key is invalid. Make sure that your Machine Secret Key is correct.',
85+
);
7186
});
7287
});
7388

Lines changed: 54 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,90 @@
1+
import type { ClerkBackendApiRequestOptions } from '../../api/request';
12
import { joinPaths } from '../../util/path';
23
import type { MachineToken } from '../resources/MachineToken';
34
import { AbstractAPI } from './AbstractApi';
45

56
const basePath = '/m2m_tokens';
67

78
type CreateMachineTokenParams = {
9+
machineSecretKey?: string;
810
secondsUntilExpiration?: number | null;
911
claims?: Record<string, unknown> | null;
1012
};
1113

1214
type RevokeMachineTokenParams = {
15+
machineSecretKey?: string;
1316
m2mTokenId: string;
1417
revocationReason?: string | null;
1518
};
1619

1720
type VerifyMachineTokenParams = {
21+
machineSecretKey?: string;
1822
secret: string;
1923
};
2024

2125
export class MachineTokenApi extends AbstractAPI {
26+
#createRequestOptions(options: ClerkBackendApiRequestOptions, machineSecretKey?: string) {
27+
if (machineSecretKey) {
28+
return {
29+
...options,
30+
headerParams: {
31+
Authorization: `Bearer ${machineSecretKey}`,
32+
},
33+
};
34+
}
35+
36+
return options;
37+
}
38+
2239
async create(params?: CreateMachineTokenParams) {
23-
const { claims = null, secondsUntilExpiration = null } = params || {};
24-
25-
return this.request<MachineToken>({
26-
method: 'POST',
27-
path: basePath,
28-
bodyParams: {
29-
secondsUntilExpiration,
30-
claims,
31-
},
32-
options: {
33-
requireMachineSecretKey: true,
40+
const { claims = null, machineSecretKey, secondsUntilExpiration = null } = params || {};
41+
42+
const requestOptions = this.#createRequestOptions(
43+
{
44+
method: 'POST',
45+
path: basePath,
46+
bodyParams: {
47+
secondsUntilExpiration,
48+
claims,
49+
},
3450
},
35-
});
51+
machineSecretKey,
52+
);
53+
54+
return this.request<MachineToken>(requestOptions);
3655
}
3756

3857
async revoke(params: RevokeMachineTokenParams) {
39-
const { m2mTokenId, revocationReason = null } = params;
58+
const { m2mTokenId, revocationReason = null, machineSecretKey } = params;
4059

4160
this.requireId(m2mTokenId);
4261

43-
return this.request<MachineToken>({
44-
method: 'POST',
45-
path: joinPaths(basePath, m2mTokenId, 'revoke'),
46-
bodyParams: {
47-
revocationReason,
62+
const requestOptions = this.#createRequestOptions(
63+
{
64+
method: 'POST',
65+
path: joinPaths(basePath, m2mTokenId, 'revoke'),
66+
bodyParams: {
67+
revocationReason,
68+
},
4869
},
49-
});
70+
machineSecretKey,
71+
);
72+
73+
return this.request<MachineToken>(requestOptions);
5074
}
5175

5276
async verifySecret(params: VerifyMachineTokenParams) {
53-
const { secret } = params;
77+
const { secret, machineSecretKey } = params;
78+
79+
const requestOptions = this.#createRequestOptions(
80+
{
81+
method: 'POST',
82+
path: joinPaths(basePath, 'verify'),
83+
bodyParams: { secret },
84+
},
85+
machineSecretKey,
86+
);
5487

55-
return this.request<MachineToken>({
56-
method: 'POST',
57-
path: joinPaths(basePath, 'verify'),
58-
bodyParams: { secret },
59-
});
88+
return this.request<MachineToken>(requestOptions);
6089
}
6190
}

packages/backend/src/api/request.ts

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import snakecaseKeys from 'snakecase-keys';
44

55
import { API_URL, API_VERSION, constants, SUPPORTED_BAPI_VERSION, USER_AGENT } from '../constants';
66
import { runtime } from '../runtime';
7-
import { assertValidMachineSecretKey, assertValidSecretKey } from '../util/optionsAssertions';
7+
import { assertValidSecretKey } from '../util/optionsAssertions';
88
import { joinPaths } from '../util/path';
99
import { deserialize } from './resources/Deserializer';
1010

@@ -27,18 +27,12 @@ type ClerkBackendApiRequestOptionsBodyParams =
2727
* @default false
2828
*/
2929
deepSnakecaseBodyParamKeys?: boolean;
30-
/**
31-
* If true, requires a machine secret key to be provided.
32-
* @default false
33-
*/
34-
requireMachineSecretKey?: boolean;
3530
};
3631
}
3732
| {
3833
bodyParams?: never;
3934
options?: {
4035
deepSnakecaseBodyParamKeys?: boolean;
41-
requireMachineSecretKey?: boolean;
4236
};
4337
};
4438

@@ -116,16 +110,12 @@ export function buildRequest(options: BuildRequestOptions) {
116110
skipApiVersionInUrl = false,
117111
} = options;
118112
const { path, method, queryParams, headerParams, bodyParams, formData, options: opts } = requestOptions;
119-
const { deepSnakecaseBodyParamKeys = false, requireMachineSecretKey = false } = opts || {};
113+
const { deepSnakecaseBodyParamKeys = false } = opts || {};
120114

121115
if (requireSecretKey) {
122116
assertValidSecretKey(secretKey);
123117
}
124118

125-
if (requireMachineSecretKey) {
126-
assertValidMachineSecretKey(machineSecretKey);
127-
}
128-
129119
const url = skipApiVersionInUrl ? joinPaths(apiUrl, path) : joinPaths(apiUrl, apiVersion, path);
130120

131121
// Build final URL with search parameters
@@ -150,12 +140,14 @@ export function buildRequest(options: BuildRequestOptions) {
150140
...headerParams,
151141
});
152142

153-
// When useMachineSecretKey is true and machineSecretKey is provided, use machine auth
154-
// Otherwise, fall back to regular secretKey auth for all existing APIs
155-
if (useMachineSecretKey && machineSecretKey) {
156-
headers.set(constants.Headers.Authorization, `Bearer ${machineSecretKey}`);
157-
} else if (secretKey) {
158-
headers.set(constants.Headers.Authorization, `Bearer ${secretKey}`);
143+
// If Authorization header already exists, preserve it.
144+
// Otherwise, use machine secret key if enabled, or fall back to regular secret key
145+
if (!headers.has(constants.Headers.Authorization)) {
146+
if (useMachineSecretKey && machineSecretKey) {
147+
headers.set(constants.Headers.Authorization, `Bearer ${machineSecretKey}`);
148+
} else if (secretKey) {
149+
headers.set(constants.Headers.Authorization, `Bearer ${secretKey}`);
150+
}
159151
}
160152

161153
let res: Response | undefined;

packages/backend/src/util/optionsAssertions.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,3 @@ export function assertValidSecretKey(val: unknown): asserts val is string {
1111
export function assertValidPublishableKey(val: unknown): asserts val is string {
1212
parsePublishableKey(val as string | undefined, { fatal: true });
1313
}
14-
15-
export function assertValidMachineSecretKey(val: unknown): asserts val is string {
16-
if (!val || typeof val !== 'string') {
17-
throw Error('Missing Clerk Machine Secret Key.');
18-
}
19-
}

0 commit comments

Comments
 (0)