@takodotid/azure-rest 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,396 @@
1
+ // src/AzureClient.ts
2
+ var AzureClient = class _AzureClient {
3
+ /**
4
+ * @param options Azure client configuration (baseUrl, credential, etc)
5
+ */
6
+ constructor(options) {
7
+ this.options = options;
8
+ Object.defineProperty(this, "token", { enumerable: false });
9
+ }
10
+ static MAX_TOKEN_RETRIES = 3;
11
+ token = null;
12
+ /**
13
+ * Sends a request to the Azure REST API, handling token refresh and retries.
14
+ * @param path The API path (relative to baseUrl)
15
+ * @param init Optional fetch options
16
+ * @returns The fetch Response object
17
+ * @throws If token refresh fails after max retries
18
+ */
19
+ async sendRequest(path, init) {
20
+ for (let i = 0; i <= _AzureClient.MAX_TOKEN_RETRIES; i++) {
21
+ if (this.token && this.token.expiresAt > /* @__PURE__ */ new Date()) break;
22
+ if (i === _AzureClient.MAX_TOKEN_RETRIES) {
23
+ throw new Error("Failed to refresh token after multiple attempts");
24
+ }
25
+ await this.refreshToken();
26
+ if (i > 0) await new Promise((res) => setTimeout(res, 100 * i));
27
+ }
28
+ if (!this.token) throw new Error("Token is unexpectedly null after refresh attempts");
29
+ return fetch(`${this.options.baseUrl}${path}`, {
30
+ ...init,
31
+ headers: {
32
+ ...this.options.credential.builder ? this.options.credential.builder(this.token) : { Authorization: `Bearer ${this.token.accessToken}` },
33
+ ...init?.headers
34
+ }
35
+ });
36
+ }
37
+ /**
38
+ * Sends a GET request to the Azure REST API.
39
+ * @param path The API path
40
+ * @param init Optional fetch options
41
+ * @returns The fetch Response object
42
+ */
43
+ get(path, init) {
44
+ return this.sendRequest(path, { ...init, method: "GET" });
45
+ }
46
+ /**
47
+ * Sends a POST request with a JSON body to the Azure REST API.
48
+ * @param path The API path
49
+ * @param body The request body (will be JSON.stringified)
50
+ * @param init Optional fetch options
51
+ * @returns The fetch Response object
52
+ */
53
+ post(path, body, init) {
54
+ return this.sendRequest(path, {
55
+ ...init,
56
+ method: "POST",
57
+ headers: {
58
+ "Content-Type": "application/json",
59
+ ...init?.headers
60
+ },
61
+ body: body !== void 0 ? JSON.stringify(body) : void 0
62
+ });
63
+ }
64
+ /**
65
+ * Sends a PUT request with a JSON body to the Azure REST API.
66
+ * @param path The API path
67
+ * @param body The request body (will be JSON.stringified)
68
+ * @param init Optional fetch options
69
+ * @returns The fetch Response object
70
+ */
71
+ put(path, body, init) {
72
+ return this.sendRequest(path, {
73
+ ...init,
74
+ method: "PUT",
75
+ headers: {
76
+ "Content-Type": "application/json",
77
+ ...init?.headers
78
+ },
79
+ body: body !== void 0 ? JSON.stringify(body) : void 0
80
+ });
81
+ }
82
+ /**
83
+ * Sends a PATCH request with a JSON body to the Azure REST API.
84
+ * @param path The API path
85
+ * @param body The request body (will be JSON.stringified)
86
+ * @param init Optional fetch options
87
+ * @returns The fetch Response object
88
+ */
89
+ patch(path, body, init) {
90
+ return this.sendRequest(path, {
91
+ ...init,
92
+ method: "PATCH",
93
+ headers: {
94
+ "Content-Type": "application/json",
95
+ ...init?.headers
96
+ },
97
+ body: body !== void 0 ? JSON.stringify(body) : void 0
98
+ });
99
+ }
100
+ /**
101
+ * Sends a DELETE request to the Azure REST API.
102
+ * @param path The API path
103
+ * @param init Optional fetch options
104
+ * @returns The fetch Response object
105
+ */
106
+ delete(path, init) {
107
+ return this.sendRequest(path, { ...init, method: "DELETE" });
108
+ }
109
+ /**
110
+ * Refreshes the Azure access token using the provided credential helper.
111
+ * @private
112
+ */
113
+ async refreshToken() {
114
+ this.token = await this.options.credential.helper.getToken(this.options.credential.scope);
115
+ }
116
+ };
117
+
118
+ // src/lib/AzureCliCredential.ts
119
+ import { execFile } from "child_process";
120
+ import process2 from "process";
121
+ var AzureCliCredential = class _AzureCliCredential {
122
+ constructor(options) {
123
+ this.options = options;
124
+ }
125
+ /**
126
+ * Gets an Azure access token using the Azure CLI.
127
+ * @param scope The resource scope for the token
128
+ * @returns An object with token and expiresAt
129
+ * @throws If CLI is not installed or not logged in
130
+ */
131
+ async getToken(scope) {
132
+ try {
133
+ const result = await this.getCliToken(scope);
134
+ const specificScope = result.stderr.match("(.*)az login --scope(.*)");
135
+ const isLoginError = result.stderr.match("(.*)az login(.*)") && !specificScope;
136
+ const isNotInstallError = result.stderr.match("az:(.*)not found") ?? result.stderr.startsWith("'az' is not recognized");
137
+ if (isNotInstallError) {
138
+ throw new Error("Azure CLI not found. Please install");
139
+ }
140
+ if (isLoginError) {
141
+ throw new Error("Please login to Azure CLI");
142
+ }
143
+ return this.parseRawOutput(result.stdout);
144
+ } catch (error) {
145
+ throw new Error(`Failed to get token: ${error.stack}`);
146
+ }
147
+ }
148
+ /**
149
+ * Runs the Azure CLI to get an access token for the given scope.
150
+ * @param scope The resource scope
151
+ * @returns Promise resolving to CLI stdout and stderr
152
+ * @private
153
+ */
154
+ async getCliToken(scope) {
155
+ return new Promise((resolve, reject) => {
156
+ try {
157
+ execFile(
158
+ "az",
159
+ ["account", "get-access-token", "--output", "json", "--resource", scope.replace(".default", ""), "--tenant", this.options.tenantId],
160
+ { cwd: process2.cwd(), shell: true, timeout: 3e4 },
161
+ (error, stdout, stderr) => {
162
+ if (error) {
163
+ reject(new Error(`Failed to get token: ${error.stack}`));
164
+ return;
165
+ }
166
+ resolve({ stdout, stderr });
167
+ }
168
+ );
169
+ } catch (error) {
170
+ reject(error);
171
+ }
172
+ });
173
+ }
174
+ /**
175
+ * Parses the raw CLI output and returns a token + expiry object.
176
+ * @param output The stdout from Azure CLI
177
+ * @returns An object with token and expiresAt
178
+ * @private
179
+ */
180
+ parseRawOutput(output) {
181
+ const response = JSON.parse(output);
182
+ const token = response.accessToken;
183
+ const expiresOnTimestamp = Number.parseInt(response.expires_on, 10) * 1e3;
184
+ if (!Number.isNaN(expiresOnTimestamp)) {
185
+ return {
186
+ accessToken: token,
187
+ expiresAt: new Date(expiresOnTimestamp),
188
+ tokenType: response.tokenType
189
+ };
190
+ }
191
+ return {
192
+ accessToken: token,
193
+ expiresAt: new Date(response.expiresOn),
194
+ tokenType: response.tokenType
195
+ };
196
+ }
197
+ /**
198
+ * Instantiates AzureCliCredential using the AZURE_TENANT_ID environment variable.
199
+ * @returns AzureCliCredential instance
200
+ */
201
+ static fromEnv() {
202
+ return new _AzureCliCredential({ tenantId: process2.env.AZURE_TENANT_ID });
203
+ }
204
+ };
205
+
206
+ // src/lib/AzureCredential.ts
207
+ var AzureCredential = class {
208
+ };
209
+
210
+ // src/lib/ManagedIdentityCredential.ts
211
+ var ManagedIdentityCredential = class _ManagedIdentityCredential {
212
+ /**
213
+ * @param options Managed identity credential options
214
+ */
215
+ constructor(options = {}) {
216
+ this.options = options;
217
+ }
218
+ /**
219
+ * Gets an Azure access token using the managed identity endpoint.
220
+ * @param scope The resource scope for the token
221
+ * @returns An object with token and expiresAt
222
+ * @throws If the endpoint is unavailable or token request fails
223
+ */
224
+ async getToken(scope) {
225
+ const endpoint = process.env.AZURE_MANAGED_IDENTITY_ENDPOINT || process.env.IDENTITY_ENDPOINT || "http://169.254.169.254/metadata/identity/oauth2/token";
226
+ const apiVersion = "2018-02-01";
227
+ const params = new URLSearchParams({
228
+ resource: scope.replace(".default", ""),
229
+ apiVersion
230
+ });
231
+ if (this.options.clientId) {
232
+ params.set("client_id", this.options.clientId);
233
+ }
234
+ const url = `${endpoint}?${params.toString()}`;
235
+ const headers = { Metadata: "true" };
236
+ const response = await fetch(url, { headers });
237
+ if (!response.ok) {
238
+ throw new Error(`ManagedIdentityCredential: Failed to get token: ${await response.text()}`);
239
+ }
240
+ const data = await response.json();
241
+ return {
242
+ accessToken: data.access_token,
243
+ clientId: data.client_id,
244
+ expiresAt: new Date(data.expires_on ? Number(data.expires_on) * 1e3 : Date.now() + 60 * 60 * 1e3),
245
+ // fallback 1h
246
+ tokenType: data.token_type
247
+ };
248
+ }
249
+ /**
250
+ * Instantiates ManagedIdentityCredential using environment variables.
251
+ * @returns ManagedIdentityCredential instance
252
+ */
253
+ static fromEnv() {
254
+ return new _ManagedIdentityCredential({ clientId: process.env.AZURE_CLIENT_ID });
255
+ }
256
+ };
257
+
258
+ // src/lib/ServicePrincipalCredential.ts
259
+ import { URLSearchParams as URLSearchParams2 } from "url";
260
+ var ServicePrincipalCredential = class _ServicePrincipalCredential {
261
+ /**
262
+ * @param options Service principal credential options
263
+ */
264
+ constructor(options) {
265
+ this.options = options;
266
+ }
267
+ /**
268
+ * Gets an Azure access token using the service principal credentials.
269
+ * @param scope The resource scope for the token
270
+ * @returns An object with token and expiresAt
271
+ * @throws If client secret is missing or token request fails
272
+ */
273
+ async getToken(scope) {
274
+ if (!this.options.clientSecret) throw new Error("ServicePrincipalCredential: The client secret is not provided.");
275
+ const url = [this.options.authorityHost?.replace(/\/$/, "") ?? "https://login.microsoftonline.com", `${this.options.tenantId}/oauth2/v2.0/token`].join("/");
276
+ try {
277
+ const searchParams = {
278
+ client_id: this.options.clientId,
279
+ grant_type: "client_credentials",
280
+ scope
281
+ };
282
+ if (this.options.federated) {
283
+ Object.assign(searchParams, {
284
+ client_assertion: this.options.clientSecret,
285
+ client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
286
+ });
287
+ } else {
288
+ Object.assign(searchParams, {
289
+ client_secret: this.options.clientSecret
290
+ });
291
+ }
292
+ const response = await fetch(url, {
293
+ method: "POST",
294
+ headers: {
295
+ Accept: "application/json",
296
+ "Content-Type": "application/x-www-form-urlencoded"
297
+ },
298
+ body: new URLSearchParams2(searchParams)
299
+ });
300
+ if (!response.ok) {
301
+ throw new Error(`Failed to get token: ${await response.text()}`);
302
+ }
303
+ const data = await response.json();
304
+ return {
305
+ accessToken: data.access_token,
306
+ clientId: data.client_id ?? this.options.clientId,
307
+ expiresAt: new Date(Date.now() + data.expires_in * 1e3),
308
+ tokenType: data.token_type
309
+ };
310
+ } catch (error) {
311
+ throw new Error(`Failed to get token: ${error.stack}`);
312
+ }
313
+ }
314
+ /**
315
+ * Instantiates ServicePrincipalCredential using environment variables.
316
+ * @returns ServicePrincipalCredential instance
317
+ */
318
+ static fromEnv() {
319
+ return new _ServicePrincipalCredential({
320
+ clientId: process.env.AZURE_CLIENT_ID,
321
+ clientSecret: process.env.AZURE_CLIENT_SECRET,
322
+ tenantId: process.env.AZURE_TENANT_ID,
323
+ federated: false
324
+ });
325
+ }
326
+ };
327
+
328
+ // src/lib/WorkloadIdentityCredential.ts
329
+ import { existsSync, readFileSync } from "fs";
330
+ var WorkloadIdentityCredential = class _WorkloadIdentityCredential {
331
+ /**
332
+ * @param options Workload identity credential options
333
+ */
334
+ constructor(options) {
335
+ this.options = options;
336
+ }
337
+ /**
338
+ * Gets an Azure access token using the federated token file.
339
+ * @param scope The resource scope for the token
340
+ * @returns An object with token and expiresAt
341
+ * @throws If the federated token file does not exist
342
+ */
343
+ async getToken(scope) {
344
+ if (!existsSync(this.options.federatedTokenFile)) {
345
+ throw new Error("WorkloadIdentityCredential: The federated token file does not exist.");
346
+ }
347
+ const token = readFileSync(this.options.federatedTokenFile, "utf-8");
348
+ const servicePrincipal = new ServicePrincipalCredential({ ...this.options, clientSecret: token, federated: true });
349
+ return servicePrincipal.getToken(scope);
350
+ }
351
+ /**
352
+ * Instantiates WorkloadIdentityCredential using environment variables.
353
+ * @returns WorkloadIdentityCredential instance
354
+ */
355
+ static fromEnv() {
356
+ return new _WorkloadIdentityCredential({
357
+ authorityHost: process.env.AZURE_AUTHORITY_HOST,
358
+ clientId: process.env.AZURE_CLIENT_ID,
359
+ federatedTokenFile: process.env.AZURE_FEDERATED_TOKEN_FILE,
360
+ tenantId: process.env.AZURE_TENANT_ID
361
+ });
362
+ }
363
+ };
364
+
365
+ // src/lib/DefaultChainedCredential.ts
366
+ var credentialChain = [WorkloadIdentityCredential, ManagedIdentityCredential, ServicePrincipalCredential, AzureCliCredential];
367
+ var DefaultChainedCredential = class {
368
+ /**
369
+ * Attempts to get an Azure access token using the first available credential in the chain.
370
+ * @param scope The resource scope for the token
371
+ * @returns An object with token and expiresAt
372
+ * @throws If all credential providers fail
373
+ */
374
+ async getToken(scope) {
375
+ const errors = [];
376
+ for (const Credential of credentialChain) {
377
+ try {
378
+ return await Credential.fromEnv().getToken(scope);
379
+ } catch (error) {
380
+ errors.push({ error, name: Credential.name });
381
+ }
382
+ }
383
+ throw new Error(`Failed to get token, errors:
384
+ ${errors.map((err) => `[${err.name}] ${err.error.message}`).join("\n")}`);
385
+ }
386
+ };
387
+ export {
388
+ AzureCliCredential,
389
+ AzureClient,
390
+ AzureCredential,
391
+ DefaultChainedCredential,
392
+ ManagedIdentityCredential,
393
+ ServicePrincipalCredential,
394
+ WorkloadIdentityCredential
395
+ };
396
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/AzureClient.ts","../src/lib/AzureCliCredential.ts","../src/lib/AzureCredential.ts","../src/lib/ManagedIdentityCredential.ts","../src/lib/ServicePrincipalCredential.ts","../src/lib/WorkloadIdentityCredential.ts","../src/lib/DefaultChainedCredential.ts"],"sourcesContent":["import type { AzureCredential, Credential } from \"./lib/AzureCredential.js\";\n\n/**\n * Options for configuring the AzureClient instance.\n *\n * @property baseUrl - The base URL for Azure REST API endpoints (e.g. https://management.azure.com)\n * @property credential - Credential configuration for authenticating requests\n * @property helper - An AzureCredential implementation for acquiring tokens\n * @property scope - The Azure resource scope for the token (e.g. https://management.azure.com/.default)\n * @property builder - (Optional) Function to build request headers from a token. If not provided, an Authorization header is set by default.\n */\nexport type AzureClientOptions = {\n\tbaseUrl: string;\n\tcredential: {\n\t\thelper: AzureCredential;\n\t\tscope: string;\n\t\tbuilder?: (token: Credential) => Record<string, string>;\n\t};\n};\n\n/**\n * Azure REST API client with credential refresh and HTTP verb helpers.\n */\nexport class AzureClient {\n\tprivate static readonly MAX_TOKEN_RETRIES = 3;\n\tprivate token: Credential | null = null;\n\n\t/**\n\t * @param options Azure client configuration (baseUrl, credential, etc)\n\t */\n\tconstructor(public options: AzureClientOptions) {\n\t\tObject.defineProperty(this, \"token\", { enumerable: false });\n\t}\n\n\t/**\n\t * Sends a request to the Azure REST API, handling token refresh and retries.\n\t * @param path The API path (relative to baseUrl)\n\t * @param init Optional fetch options\n\t * @returns The fetch Response object\n\t * @throws If token refresh fails after max retries\n\t */\n\tpublic async sendRequest(path: string, init?: RequestInit): Promise<Response> {\n\t\tfor (let i = 0; i <= AzureClient.MAX_TOKEN_RETRIES; i++) {\n\t\t\tif (this.token && this.token.expiresAt > new Date()) break;\n\t\t\tif (i === AzureClient.MAX_TOKEN_RETRIES) {\n\t\t\t\tthrow new Error(\"Failed to refresh token after multiple attempts\");\n\t\t\t}\n\t\t\tawait this.refreshToken();\n\t\t\tif (i > 0) await new Promise(res => setTimeout(res, 100 * i));\n\t\t}\n\n\t\tif (!this.token) throw new Error(\"Token is unexpectedly null after refresh attempts\");\n\n\t\treturn fetch(`${this.options.baseUrl}${path}`, {\n\t\t\t...init,\n\t\t\theaders: {\n\t\t\t\t...(this.options.credential.builder ? this.options.credential.builder(this.token) : { Authorization: `Bearer ${this.token.accessToken}` }),\n\t\t\t\t...init?.headers\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Sends a GET request to the Azure REST API.\n\t * @param path The API path\n\t * @param init Optional fetch options\n\t * @returns The fetch Response object\n\t */\n\tpublic get(path: string, init?: RequestInit) {\n\t\treturn this.sendRequest(path, { ...init, method: \"GET\" });\n\t}\n\n\t/**\n\t * Sends a POST request with a JSON body to the Azure REST API.\n\t * @param path The API path\n\t * @param body The request body (will be JSON.stringified)\n\t * @param init Optional fetch options\n\t * @returns The fetch Response object\n\t */\n\tpublic post(path: string, body?: any, init?: RequestInit) {\n\t\treturn this.sendRequest(path, {\n\t\t\t...init,\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t...init?.headers\n\t\t\t},\n\t\t\tbody: body !== undefined ? JSON.stringify(body) : undefined\n\t\t});\n\t}\n\n\t/**\n\t * Sends a PUT request with a JSON body to the Azure REST API.\n\t * @param path The API path\n\t * @param body The request body (will be JSON.stringified)\n\t * @param init Optional fetch options\n\t * @returns The fetch Response object\n\t */\n\tpublic put(path: string, body?: any, init?: RequestInit) {\n\t\treturn this.sendRequest(path, {\n\t\t\t...init,\n\t\t\tmethod: \"PUT\",\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t...init?.headers\n\t\t\t},\n\t\t\tbody: body !== undefined ? JSON.stringify(body) : undefined\n\t\t});\n\t}\n\n\t/**\n\t * Sends a PATCH request with a JSON body to the Azure REST API.\n\t * @param path The API path\n\t * @param body The request body (will be JSON.stringified)\n\t * @param init Optional fetch options\n\t * @returns The fetch Response object\n\t */\n\tpublic patch(path: string, body?: any, init?: RequestInit) {\n\t\treturn this.sendRequest(path, {\n\t\t\t...init,\n\t\t\tmethod: \"PATCH\",\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t...init?.headers\n\t\t\t},\n\t\t\tbody: body !== undefined ? JSON.stringify(body) : undefined\n\t\t});\n\t}\n\n\t/**\n\t * Sends a DELETE request to the Azure REST API.\n\t * @param path The API path\n\t * @param init Optional fetch options\n\t * @returns The fetch Response object\n\t */\n\tpublic delete(path: string, init?: RequestInit) {\n\t\treturn this.sendRequest(path, { ...init, method: \"DELETE\" });\n\t}\n\n\t/**\n\t * Refreshes the Azure access token using the provided credential helper.\n\t * @private\n\t */\n\tprivate async refreshToken(): Promise<void> {\n\t\tthis.token = await this.options.credential.helper.getToken(this.options.credential.scope);\n\t}\n}\n","import { execFile } from \"node:child_process\";\nimport process from \"node:process\";\nimport type { AzureCredential } from \"./AzureCredential.js\";\n\n/**\n * The raw response returned by Azure CLI when requesting an access token.\n *\n * @property accessToken - The access token string\n * @property expiresOn - Expiry date in RFC3339 format (legacy)\n * @property expires_on - Expiry as seconds since epoch (preferred)\n * @property subscription - (Optional) Subscription ID, may not be present\n * @property tenant - Tenant ID\n * @property tokenType - Token type (usually 'Bearer')\n */\nexport type CLITokenResponse = {\n\taccessToken: string;\n\texpiresOn: string;\n\texpires_on: string;\n\tsubscription?: string;\n\ttenant: string;\n\ttokenType: string;\n};\n\n/**\n * Options for AzureCliCredential.\n *\n * @property tenantId - The Azure tenant ID to use for authentication\n */\nexport type AzureCLICredentialOptions = {\n\ttenantId: string;\n};\n\nexport class AzureCliCredential implements AzureCredential {\n\tpublic constructor(public options: AzureCLICredentialOptions) {}\n\n\t/**\n\t * Gets an Azure access token using the Azure CLI.\n\t * @param scope The resource scope for the token\n\t * @returns An object with token and expiresAt\n\t * @throws If CLI is not installed or not logged in\n\t */\n\tpublic async getToken(scope: string) {\n\t\ttry {\n\t\t\tconst result = await this.getCliToken(scope);\n\n\t\t\tconst specificScope = result.stderr.match(\"(.*)az login --scope(.*)\");\n\t\t\tconst isLoginError = result.stderr.match(\"(.*)az login(.*)\") && !specificScope;\n\t\t\tconst isNotInstallError = result.stderr.match(\"az:(.*)not found\") ?? result.stderr.startsWith(\"'az' is not recognized\");\n\n\t\t\tif (isNotInstallError) {\n\t\t\t\tthrow new Error(\"Azure CLI not found. Please install\");\n\t\t\t}\n\t\t\tif (isLoginError) {\n\t\t\t\tthrow new Error(\"Please login to Azure CLI\");\n\t\t\t}\n\n\t\t\treturn this.parseRawOutput(result.stdout);\n\t\t} catch (error) {\n\t\t\tthrow new Error(`Failed to get token: ${(error as Error).stack}`);\n\t\t}\n\t}\n\n\t/**\n\t * Runs the Azure CLI to get an access token for the given scope.\n\t * @param scope The resource scope\n\t * @returns Promise resolving to CLI stdout and stderr\n\t * @private\n\t */\n\tprivate async getCliToken(scope: string) {\n\t\treturn new Promise<{ stderr: string; stdout: string }>((resolve, reject) => {\n\t\t\ttry {\n\t\t\t\texecFile(\n\t\t\t\t\t\"az\",\n\t\t\t\t\t[\"account\", \"get-access-token\", \"--output\", \"json\", \"--resource\", scope.replace(\".default\", \"\"), \"--tenant\", this.options.tenantId],\n\t\t\t\t\t{ cwd: process.cwd(), shell: true, timeout: 30_000 },\n\t\t\t\t\t(error, stdout, stderr) => {\n\t\t\t\t\t\tif (error) {\n\t\t\t\t\t\t\treject(new Error(`Failed to get token: ${error.stack}`));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tresolve({ stdout, stderr });\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t} catch (error) {\n\t\t\t\treject(error as Error);\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Parses the raw CLI output and returns a token + expiry object.\n\t * @param output The stdout from Azure CLI\n\t * @returns An object with token and expiresAt\n\t * @private\n\t */\n\tprivate parseRawOutput(output: string) {\n\t\tconst response = JSON.parse(output) as CLITokenResponse;\n\t\tconst token = response.accessToken;\n\n\t\t// if available, expires_on will be a number representing seconds since epoch.\n\t\t// ensure it's a number or NaN\n\t\tconst expiresOnTimestamp = Number.parseInt(response.expires_on, 10) * 1_000;\n\t\tif (!Number.isNaN(expiresOnTimestamp)) {\n\t\t\treturn {\n\t\t\t\taccessToken: token,\n\t\t\t\texpiresAt: new Date(expiresOnTimestamp),\n\t\t\t\ttokenType: response.tokenType\n\t\t\t};\n\t\t}\n\n\t\t// fallback to the older expiresOn - an RFC3339 date string\n\t\treturn {\n\t\t\taccessToken: token,\n\t\t\texpiresAt: new Date(response.expiresOn),\n\t\t\ttokenType: response.tokenType\n\t\t};\n\t}\n\n\t/**\n\t * Instantiates AzureCliCredential using the AZURE_TENANT_ID environment variable.\n\t * @returns AzureCliCredential instance\n\t */\n\tpublic static fromEnv() {\n\t\treturn new AzureCliCredential({ tenantId: process.env.AZURE_TENANT_ID });\n\t}\n}\n\ndeclare global {\n\tnamespace NodeJS {\n\t\tinterface ProcessEnv {\n\t\t\tAZURE_TENANT_ID: string;\n\t\t}\n\t}\n}\n","/**\n * Represents an Azure access token and its expiration.\n */\nexport type Credential = { accessToken: string; clientId?: string; expiresAt: Date; tokenType: string };\n\n/**\n * Abstract credential class for acquiring Azure tokens.\n * Implement this to provide custom authentication logic.\n */\nexport abstract class AzureCredential {\n\t/**\n\t * Gets an Azure access token for the given scope.\n\t * @param scope The resource or scope for which the token is requested\n\t * @returns A promise resolving to an AzureToken\n\t */\n\tpublic abstract getToken(scope: string): Promise<Credential>;\n}\n","import type { AzureCredential } from \"./AzureCredential.js\";\nimport type { OAuth2TokenResponse } from \"./ServicePrincipalCredential.js\";\n\n/**\n * Options for configuring ManagedIdentityCredential.\n *\n * @property clientId - (Optional) The user-assigned managed identity client ID\n */\nexport type ManagedIdentityCredentialOptions = {\n\tclientId?: string;\n};\n\n/**\n * AzureCredential implementation for Azure Managed Identity (MSI).\n *\n * Supports both system-assigned and user-assigned managed identities.\n * Works on Azure VM, App Service, Container Apps, etc.\n */\nexport class ManagedIdentityCredential implements AzureCredential {\n\t/**\n\t * @param options Managed identity credential options\n\t */\n\tpublic constructor(public options: ManagedIdentityCredentialOptions = {}) {}\n\n\t/**\n\t * Gets an Azure access token using the managed identity endpoint.\n\t * @param scope The resource scope for the token\n\t * @returns An object with token and expiresAt\n\t * @throws If the endpoint is unavailable or token request fails\n\t */\n\tpublic async getToken(scope: string) {\n\t\tconst endpoint = process.env.AZURE_MANAGED_IDENTITY_ENDPOINT || process.env.IDENTITY_ENDPOINT || \"http://169.254.169.254/metadata/identity/oauth2/token\";\n\t\tconst apiVersion = \"2018-02-01\";\n\t\tconst params = new URLSearchParams({\n\t\t\tresource: scope.replace(\".default\", \"\"),\n\t\t\tapiVersion\n\t\t});\n\t\tif (this.options.clientId) {\n\t\t\tparams.set(\"client_id\", this.options.clientId);\n\t\t}\n\n\t\tconst url = `${endpoint}?${params.toString()}`;\n\t\tconst headers = { Metadata: \"true\" };\n\n\t\tconst response = await fetch(url, { headers });\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(`ManagedIdentityCredential: Failed to get token: ${await response.text()}`);\n\t\t}\n\n\t\tconst data = (await response.json()) as OAuth2TokenResponse;\n\t\treturn {\n\t\t\taccessToken: data.access_token,\n\t\t\tclientId: data.client_id,\n\t\t\texpiresAt: new Date(data.expires_on ? Number(data.expires_on) * 1000 : Date.now() + 60 * 60 * 1000), // fallback 1h\n\t\t\ttokenType: data.token_type\n\t\t};\n\t}\n\n\t/**\n\t * Instantiates ManagedIdentityCredential using environment variables.\n\t * @returns ManagedIdentityCredential instance\n\t */\n\tpublic static fromEnv() {\n\t\treturn new ManagedIdentityCredential({ clientId: process.env.AZURE_CLIENT_ID });\n\t}\n}\n\ndeclare global {\n\tnamespace NodeJS {\n\t\tinterface ProcessEnv {\n\t\t\tAZURE_MANAGED_IDENTITY_ENDPOINT?: string;\n\t\t\tIDENTITY_ENDPOINT?: string;\n\t\t}\n\t}\n}\n","import { URLSearchParams } from \"node:url\";\nimport type { AzureCredential } from \"./AzureCredential.js\";\n\n/**\n * The OAuth2 token response returned by Azure AD and Managed Identity endpoints.\n *\n * @property access_token - The access token string\n * @property client_id - The client/application ID (optional, present in MSI)\n * @property expires_in - Seconds until token expiry\n * @property expires_on - Expiry time (epoch seconds, as string)\n * @property ext_expires_in - Extended expiry in seconds\n * @property not_before - Not before time (epoch seconds, as string)\n * @property resource - The resource for which the token is issued\n * @property token_type - The type of token (usually 'Bearer')\n */\nexport type OAuth2TokenResponse = {\n\taccess_token: string;\n\tclient_id?: string;\n\texpires_in: number;\n\texpires_on: string;\n\text_expires_in: number;\n\tnot_before: string;\n\tresource: string;\n\ttoken_type: string;\n};\n\n/**\n * Options for configuring ServicePrincipalCredential.\n *\n * @property clientId - The Azure AD application (client) ID\n * @property clientSecret - The client secret or JWT assertion (for federated)\n * @property tenantId - The Azure AD tenant ID\n * @property authorityHost - (Optional) The Azure AD authority host\n * @property federated - Whether to use federated (JWT) auth\n */\nexport type ServicePrincipalCredentialOption = {\n\tclientId: string;\n\tclientSecret?: string;\n\ttenantId: string;\n\tauthorityHost?: string;\n\tfederated: boolean;\n};\n\n/**\n * AzureCredential implementation for authenticating with a Service Principal (client secret or federated/JWT).\n */\nexport class ServicePrincipalCredential implements AzureCredential {\n\t/**\n\t * @param options Service principal credential options\n\t */\n\tpublic constructor(public options: ServicePrincipalCredentialOption) {}\n\n\t/**\n\t * Gets an Azure access token using the service principal credentials.\n\t * @param scope The resource scope for the token\n\t * @returns An object with token and expiresAt\n\t * @throws If client secret is missing or token request fails\n\t */\n\tpublic async getToken(scope: string) {\n\t\tif (!this.options.clientSecret) throw new Error(\"ServicePrincipalCredential: The client secret is not provided.\");\n\n\t\t// Remove trailing slash from identityAuthorityHost\n\t\tconst url = [this.options.authorityHost?.replace(/\\/$/, \"\") ?? \"https://login.microsoftonline.com\", `${this.options.tenantId}/oauth2/v2.0/token`].join(\"/\");\n\n\t\ttry {\n\t\t\tconst searchParams = {\n\t\t\t\tclient_id: this.options.clientId,\n\t\t\t\tgrant_type: \"client_credentials\",\n\t\t\t\tscope\n\t\t\t};\n\n\t\t\t// If federated, use client_assertion and client_assertion_type\n\t\t\t// Otherwise, use client_secret\n\t\t\tif (this.options.federated) {\n\t\t\t\tObject.assign(searchParams, {\n\t\t\t\t\tclient_assertion: this.options.clientSecret,\n\t\t\t\t\tclient_assertion_type: \"urn:ietf:params:oauth:client-assertion-type:jwt-bearer\"\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tObject.assign(searchParams, {\n\t\t\t\t\tclient_secret: this.options.clientSecret\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst response = await fetch(url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAccept: \"application/json\",\n\t\t\t\t\t\"Content-Type\": \"application/x-www-form-urlencoded\"\n\t\t\t\t},\n\t\t\t\tbody: new URLSearchParams(searchParams)\n\t\t\t});\n\n\t\t\tif (!response.ok) {\n\t\t\t\tthrow new Error(`Failed to get token: ${await response.text()}`);\n\t\t\t}\n\n\t\t\tconst data = (await response.json()) as OAuth2TokenResponse;\n\n\t\t\treturn {\n\t\t\t\taccessToken: data.access_token,\n\t\t\t\tclientId: data.client_id ?? this.options.clientId,\n\t\t\t\texpiresAt: new Date(Date.now() + data.expires_in * 1_000),\n\t\t\t\ttokenType: data.token_type\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tthrow new Error(`Failed to get token: ${(error as Error).stack}`);\n\t\t}\n\t}\n\n\t/**\n\t * Instantiates ServicePrincipalCredential using environment variables.\n\t * @returns ServicePrincipalCredential instance\n\t */\n\tpublic static fromEnv() {\n\t\treturn new ServicePrincipalCredential({\n\t\t\tclientId: process.env.AZURE_CLIENT_ID,\n\t\t\tclientSecret: process.env.AZURE_CLIENT_SECRET,\n\t\t\ttenantId: process.env.AZURE_TENANT_ID,\n\t\t\tfederated: false\n\t\t});\n\t}\n}\n\ndeclare global {\n\tnamespace NodeJS {\n\t\tinterface ProcessEnv {\n\t\t\tAZURE_CLIENT_ID: string;\n\t\t\tAZURE_CLIENT_SECRET: string;\n\t\t\tAZURE_TENANT_ID: string;\n\t\t}\n\t}\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport type { AzureCredential } from \"./AzureCredential.js\";\nimport { ServicePrincipalCredential } from \"./ServicePrincipalCredential.js\";\n\n/**\n * Options for configuring WorkloadIdentityCredential.\n *\n * @property clientId - The Azure AD application (client) ID\n * @property federatedTokenFile - Path to the federated token file (OIDC/JWT)\n * @property tenantId - The Azure AD tenant ID\n * @property authorityHost - (Optional) The Azure AD authority host\n */\nexport type WorkloadIdentityCredentialOption = {\n\tclientId: string;\n\tfederatedTokenFile: string;\n\ttenantId: string;\n\tauthorityHost?: string;\n};\n\n/**\n * AzureCredential implementation for Azure Workload Identity (OIDC federated token).\n *\n * Reads a federated token from file and authenticates as a service principal using JWT assertion.\n */\nexport class WorkloadIdentityCredential implements AzureCredential {\n\t/**\n\t * @param options Workload identity credential options\n\t */\n\tpublic constructor(public options: WorkloadIdentityCredentialOption) {}\n\n\t/**\n\t * Gets an Azure access token using the federated token file.\n\t * @param scope The resource scope for the token\n\t * @returns An object with token and expiresAt\n\t * @throws If the federated token file does not exist\n\t */\n\tpublic async getToken(scope: string) {\n\t\tif (!existsSync(this.options.federatedTokenFile)) {\n\t\t\tthrow new Error(\"WorkloadIdentityCredential: The federated token file does not exist.\");\n\t\t}\n\n\t\tconst token = readFileSync(this.options.federatedTokenFile, \"utf-8\");\n\t\tconst servicePrincipal = new ServicePrincipalCredential({ ...this.options, clientSecret: token, federated: true });\n\n\t\treturn servicePrincipal.getToken(scope);\n\t}\n\n\t/**\n\t * Instantiates WorkloadIdentityCredential using environment variables.\n\t * @returns WorkloadIdentityCredential instance\n\t */\n\tpublic static fromEnv() {\n\t\treturn new WorkloadIdentityCredential({\n\t\t\tauthorityHost: process.env.AZURE_AUTHORITY_HOST,\n\t\t\tclientId: process.env.AZURE_CLIENT_ID,\n\t\t\tfederatedTokenFile: process.env.AZURE_FEDERATED_TOKEN_FILE,\n\t\t\ttenantId: process.env.AZURE_TENANT_ID\n\t\t});\n\t}\n}\n\ndeclare global {\n\tnamespace NodeJS {\n\t\tinterface ProcessEnv {\n\t\t\tAZURE_AUTHORITY_HOST: string;\n\t\t\tAZURE_CLIENT_ID: string;\n\t\t\tAZURE_FEDERATED_TOKEN_FILE: string;\n\t\t\tAZURE_TENANT_ID: string;\n\t\t}\n\t}\n}\n","import { AzureCliCredential } from \"./AzureCliCredential.js\";\nimport type { AzureCredential } from \"./AzureCredential.js\";\nimport { ManagedIdentityCredential } from \"./ManagedIdentityCredential.js\";\nimport { ServicePrincipalCredential } from \"./ServicePrincipalCredential.js\";\nimport { WorkloadIdentityCredential } from \"./WorkloadIdentityCredential.js\";\n\nconst credentialChain = [WorkloadIdentityCredential, ManagedIdentityCredential, ServicePrincipalCredential, AzureCliCredential];\n\n/**\n * DefaultChainedCredential tries multiple credential providers in order until one succeeds.\n *\n * The chain is: WorkloadIdentityCredential → ManagedIdentityCredential → ServicePrincipalCredential → AzureCliCredential.\n * Useful for local dev, CI, and cloud environments with minimal config.\n */\nexport class DefaultChainedCredential implements AzureCredential {\n\t/**\n\t * Attempts to get an Azure access token using the first available credential in the chain.\n\t * @param scope The resource scope for the token\n\t * @returns An object with token and expiresAt\n\t * @throws If all credential providers fail\n\t */\n\tpublic async getToken(scope: string) {\n\t\tconst errors: { name: string; error: Error }[] = [];\n\t\tfor (const Credential of credentialChain) {\n\t\t\ttry {\n\t\t\t\treturn await Credential.fromEnv().getToken(scope);\n\t\t\t} catch (error) {\n\t\t\t\terrors.push({ error: error as Error, name: Credential.name });\n\t\t\t}\n\t\t}\n\n\t\tthrow new Error(`Failed to get token, errors:\\n${errors.map(err => `[${err.name}] ${err.error.message}`).join(\"\\n\")}`);\n\t}\n}\n"],"mappings":"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","names":["process","URLSearchParams"]}
package/lefthook.yaml ADDED
@@ -0,0 +1,11 @@
1
+ pre-commit:
2
+ commands:
3
+ check:
4
+ glob: "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}"
5
+ run: pnpm check --write --no-errors-on-unmatched --files-ignore-unknown=true --colors=off {staged_files}
6
+ stage_fixed: true
7
+
8
+ commit-msg:
9
+ commands:
10
+ commitlint:
11
+ run: pnpm commitlint --edit {1}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@takodotid/azure-rest",
3
+ "version": "0.1.0",
4
+ "description": "Minimal Azure REST client with Entra ID (formerly AAD) authentication. Zero external dependencies.",
5
+ "keywords": [
6
+ "azure",
7
+ "rest",
8
+ "entra-id",
9
+ "aad",
10
+ "authentication",
11
+ "minimal",
12
+ "zero-dependencies"
13
+ ],
14
+ "license": "MIT",
15
+ "author": "PT Hobimu Jadi Cuan (Tako) <developer@tako.id>",
16
+ "contributors": [
17
+ {
18
+ "name": "Hazmi Alfarizqi",
19
+ "email": "hzmi@tako.id",
20
+ "url": "https://github.com/Hazmi35"
21
+ }
22
+ ],
23
+ "type": "module",
24
+ "main": "dist/index.js",
25
+ "exports": {
26
+ ".": {
27
+ "import": "./dist/index.js",
28
+ "require": "./dist/index.cjs"
29
+ }
30
+ },
31
+ "commitlint": {
32
+ "extends": [
33
+ "@commitlint/config-conventional"
34
+ ],
35
+ "rules": {
36
+ "body-max-line-length": [
37
+ 0
38
+ ]
39
+ }
40
+ },
41
+ "devDependencies": {
42
+ "@biomejs/biome": "^2.0.6",
43
+ "@commitlint/cli": "^19.8.1",
44
+ "@commitlint/config-conventional": "^19.8.1",
45
+ "@tsconfig/node-lts": "^22.0.2",
46
+ "@types/node": "^24.0.10",
47
+ "lefthook": "^1.11.16",
48
+ "tsup": "^8.5.0",
49
+ "typescript": "^5.8.3"
50
+ },
51
+ "scripts": {
52
+ "check": "biome check",
53
+ "check:ci": "biome ci",
54
+ "build": "tsc && tsup"
55
+ }
56
+ }
@@ -0,0 +1,3 @@
1
+ onlyBuiltDependencies:
2
+ - esbuild
3
+ - lefthook
@@ -0,0 +1,147 @@
1
+ import type { AzureCredential, Credential } from "./lib/AzureCredential.js";
2
+
3
+ /**
4
+ * Options for configuring the AzureClient instance.
5
+ *
6
+ * @property baseUrl - The base URL for Azure REST API endpoints (e.g. https://management.azure.com)
7
+ * @property credential - Credential configuration for authenticating requests
8
+ * @property helper - An AzureCredential implementation for acquiring tokens
9
+ * @property scope - The Azure resource scope for the token (e.g. https://management.azure.com/.default)
10
+ * @property builder - (Optional) Function to build request headers from a token. If not provided, an Authorization header is set by default.
11
+ */
12
+ export type AzureClientOptions = {
13
+ baseUrl: string;
14
+ credential: {
15
+ helper: AzureCredential;
16
+ scope: string;
17
+ builder?: (token: Credential) => Record<string, string>;
18
+ };
19
+ };
20
+
21
+ /**
22
+ * Azure REST API client with credential refresh and HTTP verb helpers.
23
+ */
24
+ export class AzureClient {
25
+ private static readonly MAX_TOKEN_RETRIES = 3;
26
+ private token: Credential | null = null;
27
+
28
+ /**
29
+ * @param options Azure client configuration (baseUrl, credential, etc)
30
+ */
31
+ constructor(public options: AzureClientOptions) {
32
+ Object.defineProperty(this, "token", { enumerable: false });
33
+ }
34
+
35
+ /**
36
+ * Sends a request to the Azure REST API, handling token refresh and retries.
37
+ * @param path The API path (relative to baseUrl)
38
+ * @param init Optional fetch options
39
+ * @returns The fetch Response object
40
+ * @throws If token refresh fails after max retries
41
+ */
42
+ public async sendRequest(path: string, init?: RequestInit): Promise<Response> {
43
+ for (let i = 0; i <= AzureClient.MAX_TOKEN_RETRIES; i++) {
44
+ if (this.token && this.token.expiresAt > new Date()) break;
45
+ if (i === AzureClient.MAX_TOKEN_RETRIES) {
46
+ throw new Error("Failed to refresh token after multiple attempts");
47
+ }
48
+ await this.refreshToken();
49
+ if (i > 0) await new Promise(res => setTimeout(res, 100 * i));
50
+ }
51
+
52
+ if (!this.token) throw new Error("Token is unexpectedly null after refresh attempts");
53
+
54
+ return fetch(`${this.options.baseUrl}${path}`, {
55
+ ...init,
56
+ headers: {
57
+ ...(this.options.credential.builder ? this.options.credential.builder(this.token) : { Authorization: `Bearer ${this.token.accessToken}` }),
58
+ ...init?.headers
59
+ }
60
+ });
61
+ }
62
+
63
+ /**
64
+ * Sends a GET request to the Azure REST API.
65
+ * @param path The API path
66
+ * @param init Optional fetch options
67
+ * @returns The fetch Response object
68
+ */
69
+ public get(path: string, init?: RequestInit) {
70
+ return this.sendRequest(path, { ...init, method: "GET" });
71
+ }
72
+
73
+ /**
74
+ * Sends a POST request with a JSON body to the Azure REST API.
75
+ * @param path The API path
76
+ * @param body The request body (will be JSON.stringified)
77
+ * @param init Optional fetch options
78
+ * @returns The fetch Response object
79
+ */
80
+ public post(path: string, body?: any, init?: RequestInit) {
81
+ return this.sendRequest(path, {
82
+ ...init,
83
+ method: "POST",
84
+ headers: {
85
+ "Content-Type": "application/json",
86
+ ...init?.headers
87
+ },
88
+ body: body !== undefined ? JSON.stringify(body) : undefined
89
+ });
90
+ }
91
+
92
+ /**
93
+ * Sends a PUT request with a JSON body to the Azure REST API.
94
+ * @param path The API path
95
+ * @param body The request body (will be JSON.stringified)
96
+ * @param init Optional fetch options
97
+ * @returns The fetch Response object
98
+ */
99
+ public put(path: string, body?: any, init?: RequestInit) {
100
+ return this.sendRequest(path, {
101
+ ...init,
102
+ method: "PUT",
103
+ headers: {
104
+ "Content-Type": "application/json",
105
+ ...init?.headers
106
+ },
107
+ body: body !== undefined ? JSON.stringify(body) : undefined
108
+ });
109
+ }
110
+
111
+ /**
112
+ * Sends a PATCH request with a JSON body to the Azure REST API.
113
+ * @param path The API path
114
+ * @param body The request body (will be JSON.stringified)
115
+ * @param init Optional fetch options
116
+ * @returns The fetch Response object
117
+ */
118
+ public patch(path: string, body?: any, init?: RequestInit) {
119
+ return this.sendRequest(path, {
120
+ ...init,
121
+ method: "PATCH",
122
+ headers: {
123
+ "Content-Type": "application/json",
124
+ ...init?.headers
125
+ },
126
+ body: body !== undefined ? JSON.stringify(body) : undefined
127
+ });
128
+ }
129
+
130
+ /**
131
+ * Sends a DELETE request to the Azure REST API.
132
+ * @param path The API path
133
+ * @param init Optional fetch options
134
+ * @returns The fetch Response object
135
+ */
136
+ public delete(path: string, init?: RequestInit) {
137
+ return this.sendRequest(path, { ...init, method: "DELETE" });
138
+ }
139
+
140
+ /**
141
+ * Refreshes the Azure access token using the provided credential helper.
142
+ * @private
143
+ */
144
+ private async refreshToken(): Promise<void> {
145
+ this.token = await this.options.credential.helper.getToken(this.options.credential.scope);
146
+ }
147
+ }