@dbx-tools/auth 0.9.31 → 0.9.36

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.
Files changed (73) hide show
  1. package/README.md +49 -97
  2. package/index.ts +8 -26
  3. package/lib/index.d.ts +8 -26
  4. package/lib/index.js +6 -22
  5. package/lib/src/{databricks-cli.d.ts → _databricks-cli.d.ts} +3 -7
  6. package/lib/src/_databricks-cli.js +172 -0
  7. package/lib/src/_errors.d.ts +5 -0
  8. package/lib/src/_errors.js +10 -0
  9. package/lib/src/{lifecycle.d.ts → _lifecycle.d.ts} +5 -5
  10. package/lib/src/_lifecycle.js +214 -0
  11. package/lib/src/_logging.d.ts +1 -1
  12. package/lib/src/_logging.js +2 -2
  13. package/lib/src/{personal-access-token.d.ts → _personal-access-token.d.ts} +1 -1
  14. package/lib/src/_personal-access-token.js +30 -0
  15. package/lib/src/_profile-config.d.ts +43 -0
  16. package/lib/src/_profile-config.js +443 -0
  17. package/lib/src/{service-principal.d.ts → _service-principal.d.ts} +1 -1
  18. package/lib/src/_service-principal.js +69 -0
  19. package/lib/src/{storage.d.ts → _storage.d.ts} +4 -6
  20. package/lib/src/_storage.js +94 -0
  21. package/lib/src/_types.d.ts +40 -0
  22. package/lib/src/_types.js +2 -0
  23. package/lib/src/client.d.ts +30 -0
  24. package/lib/src/client.js +223 -0
  25. package/lib/src/config.d.ts +36 -0
  26. package/lib/src/config.js +15 -0
  27. package/lib/src/profile.d.ts +11 -0
  28. package/lib/src/profile.js +21 -0
  29. package/lib/tsconfig.tsbuildinfo +1 -1
  30. package/package.json +34 -10
  31. package/src/{databricks-cli.ts → _databricks-cli.ts} +20 -109
  32. package/src/_errors.ts +11 -0
  33. package/src/{lifecycle.ts → _lifecycle.ts} +24 -48
  34. package/src/_logging.ts +2 -2
  35. package/src/{personal-access-token.ts → _personal-access-token.ts} +1 -1
  36. package/src/_profile-config.ts +545 -0
  37. package/src/{service-principal.ts → _service-principal.ts} +2 -2
  38. package/src/_storage.ts +109 -0
  39. package/src/_types.ts +45 -0
  40. package/src/client.ts +291 -0
  41. package/src/config.ts +44 -0
  42. package/src/profile.ts +33 -0
  43. package/lib/exports.d.ts +0 -2
  44. package/lib/exports.js +0 -3
  45. package/lib/src/_profile-core.d.ts +0 -28
  46. package/lib/src/_profile-core.js +0 -216
  47. package/lib/src/_profile.d.ts +0 -15
  48. package/lib/src/_profile.js +0 -236
  49. package/lib/src/databricks-auth.d.ts +0 -11
  50. package/lib/src/databricks-auth.js +0 -314
  51. package/lib/src/databricks-cli.js +0 -239
  52. package/lib/src/errors.d.ts +0 -7
  53. package/lib/src/errors.js +0 -14
  54. package/lib/src/generated/databricks-cli-assets.json +0 -33
  55. package/lib/src/http-client.d.ts +0 -23
  56. package/lib/src/http-client.js +0 -116
  57. package/lib/src/lifecycle.js +0 -229
  58. package/lib/src/node-storage.d.ts +0 -20
  59. package/lib/src/node-storage.js +0 -207
  60. package/lib/src/personal-access-token.js +0 -30
  61. package/lib/src/service-principal.js +0 -69
  62. package/lib/src/storage.js +0 -98
  63. package/lib/src/types.d.ts +0 -157
  64. package/lib/src/types.js +0 -67
  65. package/src/_profile-core.ts +0 -258
  66. package/src/_profile.ts +0 -319
  67. package/src/databricks-auth.ts +0 -416
  68. package/src/errors.ts +0 -16
  69. package/src/generated/databricks-cli-assets.json +0 -33
  70. package/src/http-client.ts +0 -147
  71. package/src/node-storage.ts +0 -230
  72. package/src/storage.ts +0 -104
  73. package/src/types.ts +0 -188
@@ -1,416 +0,0 @@
1
- import { homedir } from "node:os";
2
- import { join } from "node:path";
3
-
4
- import * as environmentUtils from "@dbx-tools/shared-core/environment-utils";
5
-
6
- import { authLogger, tokenMetadata } from "./_logging.ts";
7
- import { AuthError } from "./errors.ts";
8
- import { DatabricksCliProvider, resolveDatabricksCli } from "./databricks-cli.ts";
9
- import { publicToken, TokenLifecycle } from "./lifecycle.ts";
10
- import { FileCredentialStore } from "./node-storage.ts";
11
- import { DatabricksPersonalAccessTokenProvider } from "./personal-access-token.ts";
12
- import {
13
- listDatabricksProfiles,
14
- machineScopes,
15
- resolveConfigFile,
16
- resolveDatabricksProfile,
17
- } from "./_profile.ts";
18
- import { DatabricksServicePrincipalProvider } from "./service-principal.ts";
19
- import { MemoryCredentialStore } from "./storage.ts";
20
- import {
21
- AuthKind,
22
- type AuthClient,
23
- AuthOptions,
24
- type CredentialStore,
25
- type DatabricksAuthOptions,
26
- type DatabricksAuthStatus,
27
- type DatabricksProfile,
28
- DEFAULT_ACCESS_TOKEN_HEADER,
29
- Storage,
30
- TargetKind,
31
- type Token,
32
- type TokenProvider,
33
- WORKSPACE_ID_HEADER,
34
- } from "./types.ts";
35
-
36
- const logger = authLogger("databricks");
37
-
38
- /** Injectable host capabilities for Databricks authentication. */
39
- export interface DatabricksAuthDependencies {
40
- environment?: Readonly<Record<string, string | undefined>>;
41
- fetch?: typeof globalThis.fetch;
42
- resolveCli?: (options: { install: boolean }) => Promise<string | undefined>;
43
- }
44
-
45
- /** Persistent Databricks auth facade over the provider-neutral lifecycle. */
46
- class PersistentAuth implements AuthClient {
47
- constructor(
48
- private readonly profileValue: DatabricksProfile,
49
- private readonly storageValue: Storage,
50
- private readonly options: DatabricksAuthOptions,
51
- private readonly environment: Readonly<Record<string, string | undefined>>,
52
- private readonly client?: TokenLifecycle,
53
- private readonly requestToken?: Token,
54
- ) {}
55
-
56
- async challenge(): Promise<void> {
57
- if (!this.client)
58
- throw new AuthError("oauth", "app_obo uses the current request token and cannot start login");
59
- logger.debug("authentication challenge requested", this.context());
60
- await this.client.login();
61
- logger.debug("authentication challenge completed", this.context());
62
- }
63
-
64
- token(login?: boolean) {
65
- logger.debug("Databricks token requested", {
66
- ...this.context(),
67
- login: login ?? "auto",
68
- source: this.requestToken ? "request" : "lifecycle",
69
- });
70
- return this.requestToken
71
- ? Promise.resolve(publicToken(this.requestToken))
72
- : this.requiredClient().tokenWithLogin(login);
73
- }
74
-
75
- async authenticate(login?: boolean): Promise<Record<string, string>> {
76
- const token = await this.token(login);
77
- const headers = {
78
- [DEFAULT_ACCESS_TOKEN_HEADER]: `${token.tokenType} ${token.accessToken}`,
79
- ...(this.profileValue.workspaceId
80
- ? { [WORKSPACE_ID_HEADER]: this.profileValue.workspaceId }
81
- : {}),
82
- };
83
- logger.debug("generated Databricks authentication headers", {
84
- ...this.context(),
85
- headerNames: Object.keys(headers),
86
- token: tokenMetadata(token),
87
- });
88
- return headers;
89
- }
90
-
91
- async authorizationHeaderForUrl(
92
- requestUrl: string,
93
- login?: boolean,
94
- ): Promise<string | undefined> {
95
- return (await this.requestHeadersForUrl(requestUrl, login))[DEFAULT_ACCESS_TOKEN_HEADER];
96
- }
97
-
98
- async requestHeadersForUrl(requestUrl: string, login?: boolean): Promise<Record<string, string>> {
99
- const request = new URL(requestUrl);
100
- const credentialOrigin = new URL(this.profileValue.host).origin;
101
- if (request.origin !== credentialOrigin) {
102
- logger.debug("skipped authentication for different origin", {
103
- ...this.context(),
104
- requestOrigin: request.origin,
105
- credentialOrigin,
106
- });
107
- return {};
108
- }
109
- logger.debug("applying authentication to matching origin", {
110
- ...this.context(),
111
- requestOrigin: request.origin,
112
- });
113
- return this.authenticate(login);
114
- }
115
-
116
- forceRefreshToken(login = true) {
117
- logger.debug("Databricks token force refresh requested", { ...this.context(), login });
118
- return this.requestToken
119
- ? Promise.resolve(publicToken(this.requestToken))
120
- : this.requiredClient().forceRefresh(login);
121
- }
122
-
123
- refreshRejectedToken(staleAccessToken: string, login = true) {
124
- logger.debug("Databricks rejected token refresh requested", {
125
- ...this.context(),
126
- login,
127
- requestToken: Boolean(this.requestToken),
128
- });
129
- return this.requestToken
130
- ? Promise.resolve(publicToken(this.requestToken))
131
- : this.requiredClient().refreshRejectedToken(staleAccessToken, login);
132
- }
133
-
134
- logout(): Promise<void> {
135
- logger.debug("Databricks logout requested", this.context());
136
- return this.client?.logout() ?? Promise.resolve();
137
- }
138
-
139
- status(): DatabricksAuthStatus {
140
- return {
141
- profile: this.profileValue.name,
142
- host: this.profileValue.host,
143
- storage: this.storageValue,
144
- };
145
- }
146
-
147
- principal(): string {
148
- return this.profileValue.principal;
149
- }
150
-
151
- workspaceId(): string | undefined {
152
- return this.profileValue.workspaceId;
153
- }
154
-
155
- authKind(): AuthKind {
156
- return this.profileValue.authKind;
157
- }
158
-
159
- profile(name?: string) {
160
- const profile =
161
- !name || name === this.profileValue.name
162
- ? this.profileValue
163
- : resolveDatabricksProfile({ ...this.options, profile: name }, this.environment);
164
- return profileSummary(profile);
165
- }
166
-
167
- listProfiles(refresh = false) {
168
- return listDatabricksProfiles(this.options.configFile, refresh, this.environment);
169
- }
170
-
171
- private requiredClient(): TokenLifecycle {
172
- if (!this.client) throw new AuthError("oauth", "Authentication lifecycle is not available");
173
- return this.client;
174
- }
175
-
176
- private context(): Record<string, unknown> {
177
- return {
178
- profile: this.profileValue.name,
179
- host: this.profileValue.host,
180
- authKind: this.profileValue.authKind,
181
- storage: this.storageValue,
182
- hasWorkspaceId: Boolean(this.profileValue.workspaceId),
183
- };
184
- }
185
- }
186
-
187
- /** Create a Databricks authentication client. */
188
- export function createAuthClient(
189
- options: DatabricksAuthOptions = { preferUserToMachine: true },
190
- storage = Storage.Auto,
191
- dependencies: DatabricksAuthDependencies = {},
192
- ): Promise<AuthClient> {
193
- return createPersistentAuth(options, storage, dependencies);
194
- }
195
-
196
- /** Resolve a Databricks profile and open built-in credential storage. */
197
- async function createPersistentAuth(
198
- options: DatabricksAuthOptions,
199
- storage: Storage,
200
- dependencies: DatabricksAuthDependencies,
201
- ): Promise<PersistentAuth> {
202
- const environment = dependencies.environment ?? process.env;
203
- const profile = resolveDatabricksProfile(options, environment);
204
- const inApp =
205
- profile.authKind === AuthKind.AppOnBehalfOf ||
206
- profile.authKind === AuthKind.AppServicePrincipal;
207
- const backend = storage === Storage.Auto ? Storage.Memory : storage;
208
- logger.debug("creating persistent Databricks auth", {
209
- profile: profile.name,
210
- host: profile.host,
211
- authKind: profile.authKind,
212
- requestedStorage: storage,
213
- resolvedStorage: backend,
214
- inApp,
215
- });
216
- const store =
217
- backend === Storage.Memory
218
- ? new MemoryCredentialStore()
219
- : new FileCredentialStore(options.cacheDir ?? join(homedir(), ".databricks"));
220
- return createPersistentAuthWithStorage(options, store, storage, dependencies, profile);
221
- }
222
-
223
- /** Resolve a Databricks profile using caller-owned generic storage. */
224
- async function createPersistentAuthWithStorage(
225
- options: DatabricksAuthOptions,
226
- store: CredentialStore,
227
- storage = store.name() === "memory" ? Storage.Memory : Storage.File,
228
- dependencies: DatabricksAuthDependencies = {},
229
- resolvedProfile?: DatabricksProfile,
230
- ): Promise<PersistentAuth> {
231
- const profile =
232
- resolvedProfile ?? resolveDatabricksProfile(options, dependencies.environment ?? process.env);
233
- if (profile.authKind === AuthKind.AppOnBehalfOf) {
234
- logger.debug("created request-scoped App OBO authentication", {
235
- profile: profile.name,
236
- host: profile.host,
237
- hasWorkspaceId: Boolean(profile.workspaceId),
238
- });
239
- return new PersistentAuth(
240
- profile,
241
- Storage.Memory,
242
- options,
243
- dependencies.environment ?? process.env,
244
- undefined,
245
- {
246
- accessToken: profile.accessToken!,
247
- tokenType: "Bearer",
248
- scopes: [...profile.scopes],
249
- },
250
- );
251
- }
252
- const provider = await providerFor(profile, options, dependencies);
253
- const client = new TokenLifecycle(
254
- profile.cacheKey,
255
- provider,
256
- store,
257
- AuthOptions.create(options.auth),
258
- );
259
- const persistent = new PersistentAuth(
260
- profile,
261
- storage === Storage.Auto ? storageFromName(store.name()) : storage,
262
- options,
263
- dependencies.environment ?? process.env,
264
- client,
265
- );
266
- logger.debug("created persistent authentication lifecycle", {
267
- profile: profile.name,
268
- host: profile.host,
269
- authKind: profile.authKind,
270
- storage: persistent.status().storage,
271
- });
272
- return persistent;
273
- }
274
-
275
- async function providerFor(
276
- profile: DatabricksProfile,
277
- options: DatabricksAuthOptions,
278
- dependencies: DatabricksAuthDependencies,
279
- ): Promise<TokenProvider> {
280
- const environment = dependencies.environment ?? process.env;
281
- const inApp = environmentUtils.isDatabricksAppEnv({ ...environment });
282
- switch (profile.authKind) {
283
- case AuthKind.UserToMachine: {
284
- const install = !inApp || options.installCliInApp === true;
285
- logger.debug("selected Databricks CLI provider", {
286
- profile: profile.name,
287
- inApp,
288
- install,
289
- });
290
- return new DatabricksCliProvider(
291
- profile.name,
292
- () =>
293
- dependencies.resolveCli
294
- ? dependencies.resolveCli({ install })
295
- : resolveDatabricksCli(environment, { install }),
296
- cliEnvironment(profile, resolveConfigFile(options.configFile, environment)),
297
- );
298
- }
299
- case AuthKind.PersonalAccessToken:
300
- logger.debug("selected personal access token provider", { profile: profile.name });
301
- return new DatabricksPersonalAccessTokenProvider(profile.accessToken!);
302
- case AuthKind.MachineToMachine:
303
- case AuthKind.AppServicePrincipal: {
304
- logger.debug("selected service-principal provider", {
305
- profile: profile.name,
306
- authKind: profile.authKind,
307
- target: profile.target,
308
- });
309
- const endpoints = await resolveOAuthEndpoints(profile, dependencies.fetch);
310
- return new DatabricksServicePrincipalProvider({
311
- tokenEndpoint: endpoints.tokenEndpoint,
312
- clientId: profile.clientId,
313
- clientSecret: profile.clientSecret!,
314
- scopes: machineScopes(profile.scopes),
315
- ...(profile.groupId ? { groupId: profile.groupId } : {}),
316
- allowInsecureRequests: isLoopbackHttp(profile.host),
317
- fetch: dependencies.fetch,
318
- });
319
- }
320
- case AuthKind.AppOnBehalfOf:
321
- throw new AuthError("config", "App OBO tokens do not use a persistent provider");
322
- }
323
- }
324
-
325
- interface AuthorizationServer {
326
- tokenEndpoint: string;
327
- }
328
-
329
- async function resolveOAuthEndpoints(
330
- profile: DatabricksProfile,
331
- fetcher: typeof globalThis.fetch = globalThis.fetch,
332
- ): Promise<AuthorizationServer> {
333
- const host = profile.host.replace(/\/$/, "");
334
- if (profile.target === TargetKind.Account) {
335
- if (!profile.accountId) throw new AuthError("config", "Account target requires account_id");
336
- const endpoints = {
337
- tokenEndpoint: `${host}/oidc/accounts/${profile.accountId}/v1/token`,
338
- };
339
- logger.debug("resolved account OAuth endpoint", {
340
- profile: profile.name,
341
- tokenOrigin: new URL(endpoints.tokenEndpoint).origin,
342
- });
343
- return endpoints;
344
- }
345
- const discovery =
346
- profile.target === TargetKind.Unified
347
- ? `${host}/oidc/accounts/${requiredAccountId(profile)}/.well-known/oauth-authorization-server`
348
- : `${host}/oidc/.well-known/oauth-authorization-server`;
349
- logger.debug("requesting OAuth discovery", {
350
- profile: profile.name,
351
- target: profile.target,
352
- discovery,
353
- });
354
- const response = await fetcher(discovery, { redirect: "manual" });
355
- logger.debug("received OAuth discovery response", {
356
- profile: profile.name,
357
- status: response.status,
358
- });
359
- if (response.status === 404)
360
- throw new AuthError("oauth", `OAuth is not supported at ${discovery}`);
361
- if (!response.ok)
362
- throw new AuthError("oauth", `OAuth discovery returned HTTP ${response.status}`);
363
- const value = (await response.json()) as Record<string, unknown>;
364
- const tokenEndpoint = stringValue(value.token_endpoint);
365
- if (!tokenEndpoint) throw new AuthError("oauth", "OAuth discovery response is incomplete");
366
- logger.debug("resolved OAuth token endpoint", {
367
- profile: profile.name,
368
- tokenOrigin: new URL(tokenEndpoint).origin,
369
- });
370
- return { tokenEndpoint };
371
- }
372
-
373
- function requiredAccountId(profile: DatabricksProfile): string {
374
- if (!profile.accountId) throw new AuthError("config", "Unified target requires account_id");
375
- return profile.accountId;
376
- }
377
-
378
- function storageFromName(name: string): Storage {
379
- return name === "memory" ? Storage.Memory : Storage.File;
380
- }
381
-
382
- function profileSummary(profile: DatabricksProfile) {
383
- return {
384
- name: profile.name,
385
- host: profile.host,
386
- ...(profile.accountId ? { accountId: profile.accountId } : {}),
387
- ...(profile.workspaceId ? { workspaceId: profile.workspaceId } : {}),
388
- target: profile.target,
389
- authKind: profile.authKind,
390
- };
391
- }
392
-
393
- function isLoopbackHttp(host: string): boolean {
394
- const url = new URL(host);
395
- return url.protocol === "http:" && ["127.0.0.1", "localhost"].includes(url.hostname);
396
- }
397
-
398
- function cliEnvironment(profile: DatabricksProfile, configFile: string): Record<string, string> {
399
- return {
400
- DATABRICKS_CONFIG_FILE: configFile,
401
- DATABRICKS_CONFIG_PROFILE: profile.name,
402
- DATABRICKS_HOST: profile.host,
403
- ...(profile.accountId ? { DATABRICKS_ACCOUNT_ID: profile.accountId } : {}),
404
- ...(profile.workspaceId ? { DATABRICKS_WORKSPACE_ID: profile.workspaceId } : {}),
405
- ...(profile.authKind === AuthKind.PersonalAccessToken
406
- ? {
407
- DATABRICKS_AUTH_TYPE: "pat",
408
- DATABRICKS_TOKEN: profile.accessToken!,
409
- }
410
- : {}),
411
- };
412
- }
413
-
414
- function stringValue(value: unknown): string | undefined {
415
- return typeof value === "string" && value.trim() ? value : undefined;
416
- }
package/src/errors.ts DELETED
@@ -1,16 +0,0 @@
1
- /** Authentication failure with a stable category for callers and language shims. */
2
- export class AuthError extends Error {
3
- constructor(
4
- readonly kind: "config" | "oauth" | "storage" | "lock-timeout" | "cli" | "http",
5
- message: string,
6
- options?: ErrorOptions,
7
- ) {
8
- super(message, options);
9
- this.name = "AuthError";
10
- }
11
- }
12
-
13
- /** Normalize arbitrary failures without erasing their original cause. */
14
- export function authError(kind: AuthError["kind"], message: string, cause?: unknown): AuthError {
15
- return cause instanceof AuthError ? cause : new AuthError(kind, message, { cause });
16
- }
@@ -1,33 +0,0 @@
1
- {
2
- "schemaVersion": 1,
3
- "dbxToolsVersion": "0.9.21",
4
- "minimumVersion": "0.296.0",
5
- "version": "1.19.0",
6
- "publishedAt": "2026-09-30T13:13:22Z",
7
- "assets": {
8
- "darwin-arm64": {
9
- "url": "https://github.com/databricks/cli/releases/download/v1.19.0/databricks_cli_1.19.0_darwin_arm64.tar.gz",
10
- "sha256": "b84c6a8c623578308a724cdd7181b92b0816c2531564f16f8de7559b2a39f51f"
11
- },
12
- "darwin-x64": {
13
- "url": "https://github.com/databricks/cli/releases/download/v1.19.0/databricks_cli_1.19.0_darwin_amd64.tar.gz",
14
- "sha256": "d29a5dc1ea7d44d0d892fd4beac05d22daa932982ca6bf98a06d52c95f5ed4cd"
15
- },
16
- "linux-arm64": {
17
- "url": "https://github.com/databricks/cli/releases/download/v1.19.0/databricks_cli_1.19.0_linux_arm64.tar.gz",
18
- "sha256": "2997ccbda57090ee48bd9282d5cb3a5bef78025db64fd1573fbd7c8726856086"
19
- },
20
- "linux-x64": {
21
- "url": "https://github.com/databricks/cli/releases/download/v1.19.0/databricks_cli_1.19.0_linux_amd64.tar.gz",
22
- "sha256": "bddf87df3d72b61384da724b40f18ab483a98af742748260097a7bb8154b070e"
23
- },
24
- "win32-arm64": {
25
- "url": "https://github.com/databricks/cli/releases/download/v1.19.0/databricks_cli_1.19.0_windows_arm64.tar.gz",
26
- "sha256": "c28d098490ea1966230c07585312f8bc66e18d468e34b06319d44e29d61c4602"
27
- },
28
- "win32-x64": {
29
- "url": "https://github.com/databricks/cli/releases/download/v1.19.0/databricks_cli_1.19.0_windows_amd64.tar.gz",
30
- "sha256": "0ded24a2b001e001c815b9c1c66dc2ef1f6c2e60c64f63c6d9afb8e9fe85afdb"
31
- }
32
- }
33
- }
@@ -1,147 +0,0 @@
1
- import { authLogger } from "./_logging.ts";
2
- import { AuthError } from "./errors.ts";
3
- import { createAuthClient, type DatabricksAuthDependencies } from "./databricks-auth.ts";
4
- import type { AuthClient, DatabricksAuthOptions } from "./types.ts";
5
- import { DEFAULT_ACCESS_TOKEN_HEADER, WORKSPACE_ID_HEADER } from "./types.ts";
6
-
7
- const logger = authLogger("http");
8
-
9
- /** Options for one Databricks JSON API request. */
10
- export interface DatabricksRequestOptions {
11
- method?: string;
12
- body?: unknown;
13
- headers?: Headers | Record<string, string> | [string, string][];
14
- login?: boolean;
15
- signal?: AbortSignal;
16
- }
17
-
18
- /** Dependency-light Databricks JSON client with one rejected-token refresh. */
19
- export class DatabricksClient {
20
- private constructor(
21
- readonly auth: AuthClient,
22
- private readonly fetcher: typeof globalThis.fetch,
23
- ) {}
24
-
25
- static async create(
26
- options: DatabricksAuthOptions = { preferUserToMachine: true },
27
- dependencies: DatabricksAuthDependencies = {},
28
- ): Promise<DatabricksClient> {
29
- const client = new DatabricksClient(
30
- await createAuthClient(options, undefined, dependencies),
31
- dependencies.fetch ?? globalThis.fetch,
32
- );
33
- logger.debug("created Databricks HTTP client", {
34
- profile: client.profile(),
35
- host: client.host(),
36
- hasWorkspaceId: Boolean(client.workspaceId()),
37
- });
38
- return client;
39
- }
40
-
41
- profile(): string {
42
- return this.auth.status().profile;
43
- }
44
-
45
- host(): string {
46
- return this.auth.status().host;
47
- }
48
-
49
- principal(): string {
50
- return this.auth.principal();
51
- }
52
-
53
- workspaceId(): string | undefined {
54
- return this.auth.workspaceId();
55
- }
56
-
57
- async request(path: string, options: DatabricksRequestOptions = {}): Promise<unknown> {
58
- const url = new URL(path, `${this.host().replace(/\/$/, "")}/`).toString();
59
- const method = options.method ?? (options.body === undefined ? "GET" : "POST");
60
- const requestUrl = new URL(url);
61
- logger.debug("sending Databricks API request", {
62
- method,
63
- origin: requestUrl.origin,
64
- path: requestUrl.pathname,
65
- hasBody: options.body !== undefined,
66
- login: options.login ?? "auto",
67
- });
68
- let authHeaders = await this.auth.authenticate(options.login);
69
- let response = await this.send(url, method, authHeaders, options);
70
- logger.debug("received Databricks API response", {
71
- method,
72
- path: requestUrl.pathname,
73
- status: response.status,
74
- attempt: 1,
75
- });
76
- if (response.status === 401) {
77
- logger.debug("refreshing rejected Databricks API credential", {
78
- method,
79
- path: requestUrl.pathname,
80
- });
81
- await this.auth.refreshRejectedToken(
82
- accessTokenFromHeaders(authHeaders),
83
- options.login ?? true,
84
- );
85
- authHeaders = await this.auth.authenticate(false);
86
- response = await this.send(url, method, authHeaders, options);
87
- logger.debug("received Databricks API response", {
88
- method,
89
- path: requestUrl.pathname,
90
- status: response.status,
91
- attempt: 2,
92
- });
93
- }
94
- const text = await response.text();
95
- if (!response.ok)
96
- throw new AuthError(
97
- "http",
98
- `Databricks API ${path} returned HTTP ${response.status}: ${text}`,
99
- );
100
- if (!text) {
101
- logger.debug("Databricks API response had no body", {
102
- method,
103
- path: requestUrl.pathname,
104
- });
105
- return undefined;
106
- }
107
- try {
108
- const value = JSON.parse(text);
109
- logger.debug("parsed Databricks API JSON response", {
110
- method,
111
- path: requestUrl.pathname,
112
- });
113
- return value;
114
- } catch (cause) {
115
- throw new AuthError("http", `Databricks API ${path} did not return JSON`, { cause });
116
- }
117
- }
118
-
119
- private send(
120
- url: string,
121
- method: string,
122
- authHeaders: Record<string, string>,
123
- options: DatabricksRequestOptions,
124
- ): Promise<Response> {
125
- const headers = new Headers(options.headers);
126
- headers.delete(DEFAULT_ACCESS_TOKEN_HEADER);
127
- headers.delete(WORKSPACE_ID_HEADER);
128
- for (const [name, value] of Object.entries(authHeaders)) headers.set(name, value);
129
- headers.set("accept", "application/json");
130
- if (options.body !== undefined) headers.set("content-type", "application/json");
131
- return this.fetcher(url, {
132
- method,
133
- headers,
134
- ...(options.body !== undefined ? { body: JSON.stringify(options.body) } : {}),
135
- signal: options.signal,
136
- });
137
- }
138
- }
139
-
140
- function accessTokenFromHeaders(headers: Record<string, string>): string {
141
- const authorization = headers[DEFAULT_ACCESS_TOKEN_HEADER];
142
- const separator = authorization?.indexOf(" ") ?? -1;
143
- if (separator < 1 || !authorization?.slice(separator + 1).trim()) {
144
- throw new AuthError("http", "Authentication headers did not contain an access token");
145
- }
146
- return authorization.slice(separator + 1).trim();
147
- }