Skip to content

Commit f49fd4c

Browse files
committed
CRED-2148: Add PAT auth support to TypeScript v2 API client
1 parent b88dc59 commit f49fd4c

7 files changed

Lines changed: 2867 additions & 88 deletions

File tree

jest.config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export default {
2+
preset: "ts-jest",
3+
testEnvironment: "node",
4+
roots: ["<rootDir>/tests"],
5+
};

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,11 @@
3737
"bdd-test:profile:built": "yarn workspace bdd-runner run start:profile --working-dir \"$(pwd)\" --built-packages \"features/v*/*.feature\""
3838
},
3939
"devDependencies": {
40+
"@types/jest": "^30.0.0",
4041
"eslint": "^9.23.0",
42+
"jest": "^30.3.0",
4143
"prettier": "^3.5.3",
44+
"ts-jest": "^29.4.6",
4245
"typescript": "^5.8.2",
4346
"typescript-eslint": "^8.29.0"
4447
},

packages/datadog-api-client/src/auth.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,26 @@ export class AppKeyAuthAuthentication implements SecurityAuthentication {
8181
}
8282
}
8383

84+
/**
85+
* Applies bearer token authentication to the request context.
86+
*/
87+
export class BearerAuthAuthentication implements SecurityAuthentication {
88+
public constructor(private token: string) {}
89+
90+
public getName(): string {
91+
return "bearerAuth";
92+
}
93+
94+
public applySecurityAuthentication(context: RequestContext): void {
95+
context.setHeaderParam("Authorization", "Bearer " + this.token);
96+
}
97+
}
98+
8499
export type AuthMethods = {
85100
AuthZ?: SecurityAuthentication;
86101
apiKeyAuth?: SecurityAuthentication;
87102
appKeyAuth?: SecurityAuthentication;
103+
bearerAuth?: SecurityAuthentication;
88104
};
89105

90106
export type ApiKeyConfiguration = string;
@@ -96,6 +112,7 @@ export type AuthMethodsConfiguration = {
96112
AuthZ?: OAuth2Configuration;
97113
apiKeyAuth?: ApiKeyConfiguration;
98114
appKeyAuth?: ApiKeyConfiguration;
115+
bearerAuth?: ApiKeyConfiguration;
99116
};
100117

101118
/**
@@ -129,5 +146,11 @@ export function configureAuthMethods(
129146
);
130147
}
131148

149+
if (config["bearerAuth"]) {
150+
authMethods["bearerAuth"] = new BearerAuthAuthentication(
151+
config["bearerAuth"],
152+
);
153+
}
154+
132155
return authMethods;
133156
}

packages/datadog-api-client/src/configuration.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,14 @@ export function createConfiguration(
218218
) {
219219
authMethods["appKeyAuth"] = process.env.DD_APP_KEY;
220220
}
221+
if (
222+
!("bearerAuth" in authMethods) &&
223+
typeof process !== "undefined" &&
224+
process.env &&
225+
process.env.DD_BEARER_TOKEN
226+
) {
227+
authMethods["bearerAuth"] = process.env.DD_BEARER_TOKEN;
228+
}
221229

222230
const configuration = new Configuration(
223231
conf.baseServer,
@@ -254,6 +262,10 @@ export function applySecurityAuthentication<
254262
requestContext: RequestContext,
255263
authMethods: AuthMethodKey[],
256264
): void {
265+
if (conf.authMethods["bearerAuth"]) {
266+
conf.authMethods["bearerAuth"].applySecurityAuthentication(requestContext);
267+
}
268+
257269
for (const authMethodName of authMethods) {
258270
const authMethod = conf.authMethods[authMethodName];
259271
if (authMethod) {

packages/datadog-api-client/src/http/isomorphic-fetch.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,9 @@ export class IsomorphicFetchHttpLibrary implements HttpLibrary {
180180
"x",
181181
);
182182
}
183+
if (headers["Authorization"]) {
184+
headers["Authorization"] = headers["Authorization"].replace(/./g, "x");
185+
}
183186

184187
const headersJSON = JSON.stringify(headers, null, 2).replace(/\n/g, "\n\t");
185188
const method = request.getHttpMethod().toString();

tests/api/auth.test.ts

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import {
2+
RequestContext,
3+
HttpMethod,
4+
} from "../../packages/datadog-api-client/src";
5+
import {
6+
BearerAuthAuthentication,
7+
configureAuthMethods,
8+
} from "../../packages/datadog-api-client/src/auth";
9+
import {
10+
createConfiguration,
11+
applySecurityAuthentication,
12+
} from "../../packages/datadog-api-client/src/configuration";
13+
14+
describe("BearerAuthAuthentication", () => {
15+
it("should set Authorization Bearer header", () => {
16+
const auth = new BearerAuthAuthentication("ddpat_test_token_123");
17+
const ctx = new RequestContext("https://example.com", HttpMethod.GET);
18+
auth.applySecurityAuthentication(ctx);
19+
expect(ctx.getHeaders()["Authorization"]).toBe(
20+
"Bearer ddpat_test_token_123",
21+
);
22+
});
23+
24+
it("should return correct name", () => {
25+
const auth = new BearerAuthAuthentication("token");
26+
expect(auth.getName()).toBe("bearerAuth");
27+
});
28+
});
29+
30+
describe("configureAuthMethods", () => {
31+
it("should configure bearerAuth when provided", () => {
32+
const methods = configureAuthMethods({
33+
bearerAuth: "ddpat_my_pat",
34+
});
35+
expect(methods.bearerAuth).toBeDefined();
36+
expect(methods.bearerAuth!.getName()).toBe("bearerAuth");
37+
});
38+
39+
it("should configure all auth methods together", () => {
40+
const methods = configureAuthMethods({
41+
apiKeyAuth: "api_key",
42+
appKeyAuth: "app_key",
43+
bearerAuth: "ddpat_pat",
44+
});
45+
expect(methods.apiKeyAuth).toBeDefined();
46+
expect(methods.appKeyAuth).toBeDefined();
47+
expect(methods.bearerAuth).toBeDefined();
48+
});
49+
});
50+
51+
describe("createConfiguration with bearer auth", () => {
52+
const originalEnv = process.env;
53+
54+
beforeEach(() => {
55+
process.env = { ...originalEnv };
56+
});
57+
58+
afterEach(() => {
59+
process.env = originalEnv;
60+
});
61+
62+
it("should read DD_BEARER_TOKEN env var", () => {
63+
process.env.DD_BEARER_TOKEN = "ddpat_env_pat";
64+
const config = createConfiguration();
65+
expect(config.authMethods.bearerAuth).toBeDefined();
66+
});
67+
68+
it("should not override explicit bearerAuth config", () => {
69+
process.env.DD_BEARER_TOKEN = "ddpat_env_pat";
70+
const config = createConfiguration({
71+
authMethods: {
72+
bearerAuth: "ddpat_explicit_pat",
73+
},
74+
});
75+
const ctx = new RequestContext("https://example.com", HttpMethod.GET);
76+
config.authMethods.bearerAuth!.applySecurityAuthentication(ctx);
77+
expect(ctx.getHeaders()["Authorization"]).toBe(
78+
"Bearer ddpat_explicit_pat",
79+
);
80+
});
81+
});
82+
83+
describe("applySecurityAuthentication with bearer auth", () => {
84+
it("should send Bearer header when only bearerAuth is configured", () => {
85+
const config = createConfiguration({
86+
authMethods: {
87+
bearerAuth: "ddpat_test_pat",
88+
},
89+
});
90+
const ctx = new RequestContext("https://example.com", HttpMethod.GET);
91+
applySecurityAuthentication(config, ctx, [
92+
"apiKeyAuth",
93+
"appKeyAuth",
94+
"AuthZ",
95+
]);
96+
expect(ctx.getHeaders()["Authorization"]).toBe("Bearer ddpat_test_pat");
97+
expect(ctx.getHeaders()["DD-API-KEY"]).toBeUndefined();
98+
expect(ctx.getHeaders()["DD-APPLICATION-KEY"]).toBeUndefined();
99+
});
100+
101+
it("should send all auth headers when all are configured", () => {
102+
const config = createConfiguration({
103+
authMethods: {
104+
apiKeyAuth: "test_api_key",
105+
appKeyAuth: "test_app_key",
106+
bearerAuth: "ddpat_test_pat",
107+
},
108+
});
109+
const ctx = new RequestContext("https://example.com", HttpMethod.GET);
110+
applySecurityAuthentication(config, ctx, [
111+
"apiKeyAuth",
112+
"appKeyAuth",
113+
"AuthZ",
114+
]);
115+
expect(ctx.getHeaders()["Authorization"]).toBe("Bearer ddpat_test_pat");
116+
expect(ctx.getHeaders()["DD-API-KEY"]).toBe("test_api_key");
117+
expect(ctx.getHeaders()["DD-APPLICATION-KEY"]).toBe("test_app_key");
118+
});
119+
120+
it("should use API key + app key when bearerAuth is not configured", () => {
121+
const config = createConfiguration({
122+
authMethods: {
123+
apiKeyAuth: "test_api_key",
124+
appKeyAuth: "test_app_key",
125+
},
126+
});
127+
const ctx = new RequestContext("https://example.com", HttpMethod.GET);
128+
applySecurityAuthentication(config, ctx, [
129+
"apiKeyAuth",
130+
"appKeyAuth",
131+
"AuthZ",
132+
]);
133+
expect(ctx.getHeaders()["DD-API-KEY"]).toBe("test_api_key");
134+
expect(ctx.getHeaders()["DD-APPLICATION-KEY"]).toBe("test_app_key");
135+
expect(ctx.getHeaders()["Authorization"]).toBeUndefined();
136+
});
137+
138+
it("should use only API key when only apiKeyAuth is required and no bearer auth", () => {
139+
const config = createConfiguration({
140+
authMethods: {
141+
apiKeyAuth: "test_api_key",
142+
},
143+
});
144+
const ctx = new RequestContext("https://example.com", HttpMethod.GET);
145+
applySecurityAuthentication(config, ctx, ["apiKeyAuth"]);
146+
expect(ctx.getHeaders()["DD-API-KEY"]).toBe("test_api_key");
147+
expect(ctx.getHeaders()["Authorization"]).toBeUndefined();
148+
});
149+
});

0 commit comments

Comments
 (0)