@dbx-tools/auth 0.9.32 → 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 (72) 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} +2 -13
  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} +12 -40
  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 → _storage.ts} +2 -31
  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/file-storage.d.ts +0 -20
  55. package/lib/src/file-storage.js +0 -207
  56. package/lib/src/generated/databricks-cli-assets.json +0 -33
  57. package/lib/src/http-client.d.ts +0 -23
  58. package/lib/src/http-client.js +0 -116
  59. package/lib/src/lifecycle.js +0 -233
  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 -112
  63. package/lib/src/types.d.ts +0 -165
  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/file-storage.ts +0 -230
  70. package/src/generated/databricks-cli-assets.json +0 -33
  71. package/src/http-client.ts +0 -147
  72. package/src/types.ts +0 -196
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dbx-tools/auth",
3
- "description": "Persistent Databricks authentication with CLI-owned user credentials, App auth, profile resolution, and portable storage adapters",
3
+ "description": "Databricks profile resolution and token or authentication-header production for Node.js and Bun",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/reggie-db/dbx-tools.git",
@@ -19,14 +19,14 @@
19
19
  "projen": "projen"
20
20
  },
21
21
  "devDependencies": {
22
- "@types/bun": "1.3.14",
23
22
  "@types/ini": "^4.1.1",
24
23
  "@types/node": "^24.6.0",
25
24
  "typescript": "^5.9.3"
26
25
  },
27
26
  "dependencies": {
28
- "@dbx-tools/core": "^0.9.32",
29
- "@dbx-tools/shared-core": "^0.9.32",
27
+ "@dbx-tools/core": "^0.9.36",
28
+ "@dbx-tools/shared-auth": "^0.9.36",
29
+ "@dbx-tools/shared-core": "^0.9.36",
30
30
  "ini": "^6.0.0",
31
31
  "oauth4webapi": "^3.8.8"
32
32
  },
@@ -41,17 +41,46 @@
41
41
  "types": "./lib/index.d.ts",
42
42
  "default": "./lib/index.js"
43
43
  },
44
+ "./client": {
45
+ "types": "./lib/src/client.d.ts",
46
+ "default": "./lib/src/client.js"
47
+ },
48
+ "./config": {
49
+ "types": "./lib/src/config.d.ts",
50
+ "default": "./lib/src/config.js"
51
+ },
52
+ "./profile": {
53
+ "types": "./lib/src/profile.d.ts",
54
+ "default": "./lib/src/profile.js"
55
+ },
44
56
  "./package.json": "./package.json"
45
57
  }
46
58
  },
47
- "version": "0.9.32",
59
+ "version": "0.9.36",
48
60
  "types": "./lib/index.d.ts",
61
+ "dbxToolsConfig": {
62
+ "tags": [
63
+ "node"
64
+ ]
65
+ },
49
66
  "type": "module",
50
67
  "exports": {
51
68
  ".": {
52
69
  "types": "./lib/index.d.ts",
53
70
  "default": "./lib/index.js"
54
71
  },
72
+ "./client": {
73
+ "types": "./lib/src/client.d.ts",
74
+ "default": "./lib/src/client.js"
75
+ },
76
+ "./config": {
77
+ "types": "./lib/src/config.d.ts",
78
+ "default": "./lib/src/config.js"
79
+ },
80
+ "./profile": {
81
+ "types": "./lib/src/profile.d.ts",
82
+ "default": "./lib/src/profile.js"
83
+ },
55
84
  "./package.json": "./package.json"
56
85
  },
57
86
  "files": [
@@ -59,10 +88,5 @@
59
88
  "src",
60
89
  "lib"
61
90
  ],
62
- "dbxToolsConfig": {
63
- "tags": [
64
- "node"
65
- ]
66
- },
67
91
  "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"bunx projen\"."
68
92
  }
@@ -1,60 +1,34 @@
1
- import { homedir } from "node:os";
2
- import { join } from "node:path";
3
-
4
- import * as bin from "@dbx-tools/core/bin";
5
1
  import * as exec from "@dbx-tools/core/exec";
6
2
  import * as stringUtils from "@dbx-tools/shared-core/string-utils";
7
3
 
8
- import cliAssets from "./generated/databricks-cli-assets.json" with { type: "json" };
4
+ import { AuthError } from "./_errors.ts";
9
5
  import { authLogger, failureMetadata, tokenMetadata } from "./_logging.ts";
10
- import { AuthError } from "./errors.ts";
11
- import type { Token, TokenProvider } from "./types.ts";
6
+ import type { Token, TokenProvider } from "./_types.ts";
12
7
 
13
8
  const resolutionCache = new Map<string, Promise<string | undefined>>();
14
9
  const logger = authLogger("cli");
15
10
 
16
- interface DatabricksCliAsset {
17
- readonly url: string;
18
- readonly sha256: string;
19
- }
20
-
21
11
  /** Deferred Databricks CLI lookup used by U2M token acquisition. */
22
12
  export type DatabricksCliResolver = () => Promise<string | undefined>;
23
13
 
24
- /** Controls whether CLI resolution may install the pinned managed executable. */
25
- export interface DatabricksCliResolutionOptions {
26
- install?: boolean;
27
- }
28
-
29
- /** Resolve a compatible Databricks CLI, installing the pinned build asset when needed. */
14
+ /** Resolve an available Databricks CLI without installing executables. */
30
15
  export function resolveDatabricksCli(
31
16
  environment: Readonly<Record<string, string | undefined>> = process.env,
32
- options: DatabricksCliResolutionOptions = {},
33
17
  ): Promise<string | undefined> {
34
18
  const candidate = stringValue(environment.DATABRICKS_CLI_PATH) ?? "databricks";
35
- const key = `${candidate}\0${homedir()}\0${process.platform}\0${process.arch}\0${options.install !== false}`;
36
- const cached = resolutionCache.get(key);
19
+ const cached = resolutionCache.get(candidate);
37
20
  if (cached) {
38
- logger.debug("reused Databricks CLI resolution", {
39
- candidate,
40
- install: options.install !== false,
41
- });
21
+ logger.debug("reused Databricks CLI resolution", { candidate });
42
22
  return cached;
43
23
  }
44
- logger.debug("resolving Databricks CLI", {
45
- candidate,
46
- install: options.install !== false,
47
- platform: process.platform,
48
- arch: process.arch,
49
- minimumVersion: cliAssets.minimumVersion,
50
- });
51
- const resolved = resolveDatabricksCliUncached(candidate, options.install !== false).catch(
52
- (error) => {
53
- resolutionCache.delete(key);
24
+ logger.debug("resolving Databricks CLI", { candidate });
25
+ const resolved = compatibleDatabricksCli(candidate)
26
+ .then((compatible) => (compatible ? candidate : undefined))
27
+ .catch((error) => {
28
+ resolutionCache.delete(candidate);
54
29
  throw error;
55
- },
56
- );
57
- resolutionCache.set(key, resolved);
30
+ });
31
+ resolutionCache.set(candidate, resolved);
58
32
  return resolved;
59
33
  }
60
34
 
@@ -64,59 +38,6 @@ export function resetDatabricksCliResolution(): void {
64
38
  logger.debug("cleared Databricks CLI resolution cache");
65
39
  }
66
40
 
67
- async function resolveDatabricksCliUncached(
68
- candidate: string,
69
- install: boolean,
70
- ): Promise<string | undefined> {
71
- if (await compatibleDatabricksCli(candidate)) {
72
- logger.debug("selected installed Databricks CLI", { executable: candidate });
73
- return candidate;
74
- }
75
- const managed = managedExecutable();
76
- if (await compatibleDatabricksCli(managed)) {
77
- logger.debug("selected managed Databricks CLI", { executable: managed });
78
- return managed;
79
- }
80
- if (!install) {
81
- logger.debug("compatible Databricks CLI unavailable and installation disabled");
82
- return undefined;
83
- }
84
- const asset = platformAsset();
85
- if (!asset) {
86
- logger.debug("no managed Databricks CLI asset for platform", {
87
- platform: process.platform,
88
- arch: process.arch,
89
- });
90
- return undefined;
91
- }
92
- const root = join(homedir(), ".databricks");
93
- const binDir = join(root, "bin");
94
- const executable = managedExecutableName();
95
- const installed = await bin.ensure("databricks", asset, {
96
- autoUnpackage: true,
97
- destination: { root, binDir, path: join(binDir, executable) },
98
- minVersion: cliAssets.minimumVersion,
99
- selector: ({ source }) => join(source, executable),
100
- versionParser: (output) => {
101
- const version = bin.parseVersion(output);
102
- return version === cliAssets.version ? version : undefined;
103
- },
104
- });
105
- logger.debug("ensured managed Databricks CLI", {
106
- executable: installed.path,
107
- version: cliAssets.version,
108
- });
109
- return installed.path;
110
- }
111
-
112
- function managedExecutable(): string {
113
- return join(homedir(), ".databricks", "bin", managedExecutableName());
114
- }
115
-
116
- function managedExecutableName(): string {
117
- return process.platform === "win32" ? "databricks.exe" : "databricks";
118
- }
119
-
120
41
  async function compatibleDatabricksCli(executable: string): Promise<boolean> {
121
42
  try {
122
43
  const result = await runProcess(executable, ["--version"], {}, 10_000);
@@ -127,16 +48,8 @@ async function compatibleDatabricksCli(executable: string): Promise<boolean> {
127
48
  });
128
49
  return false;
129
50
  }
130
- const version = bin.parseVersion(result);
131
- const compatible =
132
- version !== undefined && bin.isVersionAtLeast(version, cliAssets.minimumVersion);
133
- logger.debug("probed Databricks CLI version", {
134
- executable,
135
- version: version ?? "unknown",
136
- minimumVersion: cliAssets.minimumVersion,
137
- compatible,
138
- });
139
- return compatible;
51
+ logger.debug("selected installed Databricks CLI", { executable });
52
+ return true;
140
53
  } catch (cause) {
141
54
  logger.debug("Databricks CLI version probe unavailable", {
142
55
  executable,
@@ -146,11 +59,6 @@ async function compatibleDatabricksCli(executable: string): Promise<boolean> {
146
59
  }
147
60
  }
148
61
 
149
- function platformAsset(): DatabricksCliAsset | undefined {
150
- const key = `${process.platform}-${process.arch}` as keyof typeof cliAssets.assets;
151
- return cliAssets.assets[key];
152
- }
153
-
154
62
  /** Run interactive Databricks CLI login for one profile. */
155
63
  export async function databricksCliLogin(
156
64
  profile: string,
@@ -164,8 +72,9 @@ export async function databricksCliLogin(
164
72
  ["auth", "login", "--profile", profile, "--timeout", `${Math.ceil(timeoutMs / 1000)}s`],
165
73
  environment,
166
74
  );
167
- if (result.exitCode !== 0)
75
+ if (result.exitCode !== 0) {
168
76
  throw new AuthError("cli", result.stderr || `databricks auth login exited ${result.exitCode}`);
77
+ }
169
78
  logger.debug("Databricks CLI login completed", { profile, executable });
170
79
  }
171
80
 
@@ -180,8 +89,9 @@ export async function databricksCliToken(
180
89
  const args = ["auth", "token", "--profile", profile, "--output", "json"];
181
90
  if (forceRefresh) args.push("--force-refresh");
182
91
  const result = await runProcess(executable, args, environment);
183
- if (result.exitCode !== 0)
92
+ if (result.exitCode !== 0) {
184
93
  throw new AuthError("cli", result.stderr || `databricks auth token exited ${result.exitCode}`);
94
+ }
185
95
  let value: Record<string, unknown>;
186
96
  try {
187
97
  value = JSON.parse(result.stdout ?? "") as Record<string, unknown>;
@@ -281,8 +191,9 @@ export class DatabricksCliProvider implements TokenProvider {
281
191
  logger.debug("resolving provider CLI lazily", { profile: this.profile });
282
192
  this.resolution = this.executableOrResolver()
283
193
  .then((executable) => {
284
- if (!executable)
194
+ if (!executable) {
285
195
  throw new AuthError("cli", "Databricks CLI is unavailable on this platform");
196
+ }
286
197
  return executable;
287
198
  })
288
199
  .catch((error) => {
package/src/_errors.ts ADDED
@@ -0,0 +1,11 @@
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" | "cli",
5
+ message: string,
6
+ options?: ErrorOptions,
7
+ ) {
8
+ super(message, options);
9
+ this.name = "AuthError";
10
+ }
11
+ }
@@ -1,6 +1,8 @@
1
- import { AuthError } from "./errors.ts";
1
+ import { AuthError } from "./_errors.ts";
2
2
  import { authLogger, credentialId, failureMetadata, tokenMetadata } from "./_logging.ts";
3
- import type { AccessToken, AuthOptions, CredentialStore, Token, TokenProvider } from "./types.ts";
3
+ import type { CredentialStore, Token, TokenProvider } from "./_types.ts";
4
+ import type { AccessToken } from "./client.ts";
5
+ import type { AuthOptions } from "./config.ts";
4
6
 
5
7
  const logger = authLogger("lifecycle");
6
8
 
@@ -10,12 +12,11 @@ export class TokenLifecycle {
10
12
  private readonly key: string,
11
13
  private readonly provider: TokenProvider,
12
14
  private readonly store: CredentialStore,
13
- private readonly options: AuthOptions,
15
+ private readonly options: Required<AuthOptions>,
14
16
  private readonly now: () => Date = () => new Date(),
15
17
  ) {
16
18
  logger.debug("created authentication lifecycle", {
17
19
  credential: credentialId(key),
18
- storage: store.name(),
19
20
  refreshBufferMs: options.refreshBufferMs,
20
21
  lockTimeoutMs: options.lockTimeoutMs,
21
22
  loginTimeoutMs: options.loginTimeoutMs,
@@ -23,17 +24,10 @@ export class TokenLifecycle {
23
24
  });
24
25
  }
25
26
 
26
- storeName(): string {
27
- return this.store.name();
28
- }
29
-
30
27
  async login(): Promise<AccessToken> {
31
28
  logger.debug("interactive login requested", this.context());
32
29
  return this.withLock(async () => {
33
- await this.store.prepareWrite();
34
- const token = validateToken(
35
- await this.provider.login(this.options.loginTimeoutMs),
36
- );
30
+ const token = validateToken(await this.provider.login(this.options.loginTimeoutMs));
37
31
  await this.store.save(this.key, token);
38
32
  logger.debug("interactive login stored credential", {
39
33
  ...this.context(),
@@ -60,12 +54,7 @@ export class TokenLifecycle {
60
54
 
61
55
  forceRefresh(login = true): Promise<AccessToken> {
62
56
  logger.debug("forced refresh requested", { ...this.context(), login });
63
- return this.refreshRejected(undefined, login);
64
- }
65
-
66
- refreshRejectedToken(staleAccessToken: string, login = true): Promise<AccessToken> {
67
- logger.debug("rejected token refresh requested", { ...this.context(), login });
68
- return this.refreshRejected(staleAccessToken, login);
57
+ return this.refresh(login);
69
58
  }
70
59
 
71
60
  async logout(): Promise<void> {
@@ -97,24 +86,9 @@ export class TokenLifecycle {
97
86
  });
98
87
  }
99
88
 
100
- private async refreshRejected(
101
- staleAccessToken: string | undefined,
102
- login: boolean,
103
- ): Promise<AccessToken> {
89
+ private async refresh(login: boolean): Promise<AccessToken> {
104
90
  return this.withLock(async () => {
105
91
  const current = await this.store.load(this.key);
106
- if (
107
- staleAccessToken &&
108
- current?.accessToken &&
109
- current.accessToken !== staleAccessToken &&
110
- isValid(current, this.now())
111
- ) {
112
- logger.debug("reused replacement for rejected credential", {
113
- ...this.context(),
114
- token: tokenMetadata(current, this.now()),
115
- });
116
- return publicToken(current);
117
- }
118
92
  return this.renew(current, login);
119
93
  });
120
94
  }
@@ -170,7 +144,6 @@ export class TokenLifecycle {
170
144
  ...this.context(),
171
145
  token: tokenMetadata(token, this.now()),
172
146
  });
173
- await this.store.prepareWrite();
174
147
  await this.store.save(this.key, token);
175
148
  logger.debug("credential saved", this.context());
176
149
  return publicToken(token);
@@ -179,9 +152,7 @@ export class TokenLifecycle {
179
152
  private canReuse(token: Token): boolean {
180
153
  if (!isValid(token, this.now())) return false;
181
154
  if (!token.expiry) return true;
182
- return (
183
- Date.parse(token.expiry) - this.now().getTime() > this.options.refreshBufferMs
184
- );
155
+ return Date.parse(token.expiry) - this.now().getTime() > this.options.refreshBufferMs;
185
156
  }
186
157
 
187
158
  private async withLock<T>(action: () => Promise<T>): Promise<T> {
@@ -213,7 +184,7 @@ export class TokenLifecycle {
213
184
  }
214
185
 
215
186
  private context(): Record<string, unknown> {
216
- return { credential: credentialId(this.key), storage: this.store.name() };
187
+ return { credential: credentialId(this.key) };
217
188
  }
218
189
  }
219
190
 
@@ -227,8 +198,9 @@ export function publicToken(token: Token): AccessToken {
227
198
  }
228
199
 
229
200
  export function validateToken(token: Token, previous?: Token): Token {
230
- if (!token.accessToken.trim())
201
+ if (!token.accessToken.trim()) {
231
202
  throw new AuthError("oauth", "Token response did not contain an access token");
203
+ }
232
204
  return {
233
205
  accessToken: token.accessToken,
234
206
  tokenType: token.tokenType || "Bearer",
package/src/_logging.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import * as hash from "@dbx-tools/shared-core/hash";
2
2
  import * as log from "@dbx-tools/shared-core/log";
3
3
 
4
- import { AuthError } from "./errors.ts";
5
- import type { Token } from "./types.ts";
4
+ import { AuthError } from "./_errors.ts";
5
+ import type { Token } from "./_types.ts";
6
6
 
7
7
  /** Auth logger with a stable capability-specific tag. */
8
8
  export function authLogger(scope: string): log.Logger {
@@ -1,5 +1,5 @@
1
1
  import { authLogger } from "./_logging.ts";
2
- import type { Token, TokenProvider } from "./types.ts";
2
+ import type { Token, TokenProvider } from "./_types.ts";
3
3
 
4
4
  const logger = authLogger("pat");
5
5