From 386c30b7958c0e4c811a8e2e7d661e18defc917c Mon Sep 17 00:00:00 2001 From: eric-wang-1990 Date: Tue, 25 Aug 2026 16:17:27 -0700 Subject: [PATCH 1/7] feat: add tokenCacheEnabled to control kernel U2M on-disk token cache Adds tokenCacheEnabled on the databricks-oauth option, mapped to the napi ConnectionOptions field on the U2M path. U2M-only; disabled by default (matches Thrift no-persistence posture); enable-flag only; the Thrift-only persistence custom-store hook is untouched and still rejected on the kernel path. Co-authored-by: Isaac Signed-off-by: eric-wang-1990 --- CONNECTION_PARAMETERS.md | 4 ++++ lib/contracts/IDBSQLClient.ts | 7 +++++++ lib/kernel/KernelAuth.ts | 5 +++++ tests/unit/kernel/auth-u2m.test.ts | 29 +++++++++++++++++++++++++++++ 4 files changed, 45 insertions(+) diff --git a/CONNECTION_PARAMETERS.md b/CONNECTION_PARAMETERS.md index ea0af4fb..33a18736 100644 --- a/CONNECTION_PARAMETERS.md +++ b/CONNECTION_PARAMETERS.md @@ -66,6 +66,7 @@ column. | `oauthClientSecret` (M2M) | `string` | ✅ | ✅ | — | M2M client-credentials secret; its **presence** is the U2M-vs-M2M flow selector on both backends (`undefined` ⇒ U2M). Thrift → `DatabricksOAuth.clientSecret`. Kernel → native `oauthClientSecret` (workspace-OIDC M2M) or remapped to `azureClientSecret` (Entra-direct `AzureSpM2m`). A blank/reserved secret is forwarded verbatim and still selects M2M (Thrift parity) — except the Azure SP arm, which rejects it. | | `azureTenantId` / `useDatabricksOAuthInAzure` | `string` / `boolean` | ✅ | ⚠️ | — | **Honored on both.** By design the kernel routes **all U2M** (no secret, any cloud) to its cloud-blind in-house OAuth U2M flow — there is no Azure-specific U2M mode, so `useDatabricksOAuthInAzure` is inert on U2M and every Azure workspace (including `.databricks.azure.us` US-gov) is always supported, on any kernel build. `useDatabricksOAuthInAzure` selects only the **M2M** mechanism on an Azure host: absent/`false` → Entra-direct service-principal M2M (native `AzureSpM2m` mode, creds ride `oauthClientId`/`oauthClientSecret`, `azureTenantId` optional — kernel auto-discovers from the workspace `/aad/auth` redirect when omitted); `true` → workspace-OIDC M2M. (`lib/kernel/KernelAuth.ts` `buildKernelConnectionOptions`.) | | `persistence` (custom OAuth token store) | `OAuthPersistence` | ✅ | ❌ | — | **Thrift-only.** Kernel throws; it auto-persists U2M tokens to `~/.config/databricks-sql-kernel/oauth/` and does not cache M2M. | +| `tokenCacheEnabled` (U2M on-disk cache) | `boolean` | ❌ | ✅ | `false` (disabled by default) | **Kernel U2M-only.** Controls the kernel's built-in on-disk token cache for U2M OAuth flows. When `true`, the refresh token is persisted (AES-256 encrypted) to `~/.config/databricks-sql-kernel/oauth/`. When `false` or omitted (the default), tokens remain in-memory only — silent-no-persist parity. No effect on M2M or other auth types. Distinct from the Thrift `persistence` custom-store hook. TODO: publish only after PR #283 (`tokenCacheEnabled` napi support) ships in `@databricks/databricks-sql-kernel`. | | `enableTokenFederation` / `federationClientId` | `boolean` / `string` | ✅ | ⚠️ | `false` / — | On the kernel backend these options apply only to `static-token`. Federation is always enabled, so `enableTokenFederation` is ignored; an omitted or empty client ID selects account-wide WIF and a non-empty ID selects SP-wide WIF. Thrift honors the boolean and also supports these options for `token-provider` and `external-token`. | ## HTTP client, proxy, retries @@ -177,6 +178,9 @@ backend, so they are read regardless of `useKernel`. Defaults are sourced from `checkServerCertificate`, `customCaCert`, `clientCert`, `clientKey` — **are** public and honored on the Thrift backend, which verifies certificates by default via `checkServerCertificate ?? true`; see the TLS / SSL section.) +3. `tokenCacheEnabled` — enables the kernel's built-in on-disk token cache for + U2M OAuth flows only (defaults to disabled for silent-no-persist parity). Distinct + from the Thrift `persistence` custom-store hook. ### Behavioral divergences to watch diff --git a/lib/contracts/IDBSQLClient.ts b/lib/contracts/IDBSQLClient.ts index 761ebafd..8a5bdef1 100644 --- a/lib/contracts/IDBSQLClient.ts +++ b/lib/contracts/IDBSQLClient.ts @@ -26,6 +26,13 @@ type AuthOptions = // U2M flow to `['sql', 'offline_access']` (parity with the Thrift driver's // `defaultOAuthScopes`), overriding the kernel's bare `all-apis offline_access`. oauthScopes?: Array; + // Enable the kernel's built-in on-disk OAuth token cache for U2M flows only. + // When `true`, the kernel persists the U2M refresh token (AES-256 encrypted) to + // `~/.config/databricks-sql-kernel/oauth/`. When `false` or omitted, tokens remain + // in-memory only (the default, for security and silent-no-persist behavior parity). + // Has no effect on M2M or other auth types. This option is distinct from the + // Thrift `persistence` custom-store hook, which is not yet supported on the kernel. + tokenCacheEnabled?: boolean; } | { authType: 'custom'; diff --git a/lib/kernel/KernelAuth.ts b/lib/kernel/KernelAuth.ts index f8c9529f..6fb54cc5 100644 --- a/lib/kernel/KernelAuth.ts +++ b/lib/kernel/KernelAuth.ts @@ -251,6 +251,7 @@ export type KernelNativeConnectionOptions = KernelSessionDefaults & oauthRedirectPort: number; oauthScopes?: Array; oauthClientId?: string; + tokenCacheEnabled?: boolean; } | { hostName: string; @@ -669,6 +670,7 @@ export function buildKernelConnectionOptions(options: ConnectionOptions): Kernel oauthScopes?: Array; azureTenantId?: string; useDatabricksOAuthInAzure?: boolean; + tokenCacheEnabled?: boolean; persistence?: unknown; }; @@ -833,6 +835,9 @@ export function buildKernelConnectionOptions(options: ConnectionOptions): Kernel // Scopes default to Thrift parity (`sql offline_access`); overridable. oauthScopes: Array.isArray(oauth.oauthScopes) && oauth.oauthScopes.length > 0 ? oauth.oauthScopes : U2M_DEFAULT_SCOPES, + // Token cache is disabled by default for security (silent-no-persist parity); + // explicitly set to false unless the caller opts in. + tokenCacheEnabled: oauth.tokenCacheEnabled ?? false, }; // clientId: Thrift uses `oauthClientId ?? default`. Forward it verbatim // when set; when absent the napi applies the same default diff --git a/tests/unit/kernel/auth-u2m.test.ts b/tests/unit/kernel/auth-u2m.test.ts index 1943e099..75149a44 100644 --- a/tests/unit/kernel/auth-u2m.test.ts +++ b/tests/unit/kernel/auth-u2m.test.ts @@ -37,6 +37,7 @@ describe('KernelAuth + KernelBackend — OAuth U2M auth flow', () => { authMode: 'OAuthU2m', oauthRedirectPort: 8030, oauthScopes: ['sql', 'offline_access'], + tokenCacheEnabled: false, }); }); @@ -122,6 +123,7 @@ describe('KernelAuth + KernelBackend — OAuth U2M auth flow', () => { authMode: 'OAuthU2m', oauthRedirectPort: 8030, oauthScopes: ['sql', 'offline_access'], + tokenCacheEnabled: false, }); }); @@ -144,9 +146,35 @@ describe('KernelAuth + KernelBackend — OAuth U2M auth flow', () => { authMode: 'OAuthU2m', oauthRedirectPort: 8030, oauthScopes: ['sql', 'offline_access'], + tokenCacheEnabled: false, }); }); + it('disables tokenCacheEnabled by default (silent-no-persist parity)', () => { + const opts: ConnectionOptions = { + host: 'example.cloud.databricks.com', + path: '/sql/1.0/warehouses/abc', + authType: 'databricks-oauth', + }; + + const native = buildKernelConnectionOptions(opts); + expect(native.authMode).to.equal('OAuthU2m'); + expect((native as { tokenCacheEnabled?: boolean }).tokenCacheEnabled).to.equal(false); + }); + + it('honors tokenCacheEnabled: true to enable the kernel on-disk token cache', () => { + const opts: ConnectionOptions = { + host: 'example.cloud.databricks.com', + path: '/sql/1.0/warehouses/abc', + authType: 'databricks-oauth', + tokenCacheEnabled: true, + } as ConnectionOptions; + + const native = buildKernelConnectionOptions(opts); + expect(native.authMode).to.equal('OAuthU2m'); + expect((native as { tokenCacheEnabled?: boolean }).tokenCacheEnabled).to.equal(true); + }); + it('rejects a `persistence` hook on U2M citing the AuthConfig::External kernel-plumbing gap', () => { const opts: ConnectionOptions = { host: 'example.cloud.databricks.com', @@ -187,6 +215,7 @@ describe('KernelAuth + KernelBackend — OAuth U2M auth flow', () => { authMode: 'OAuthU2m', oauthRedirectPort: 8030, oauthScopes: ['sql', 'offline_access'], + tokenCacheEnabled: false, }); await session.close(); From 1f7dcde733861504e7e33b607894d45e2a56d121 Mon Sep 17 00:00:00 2001 From: "peco-engineer-bot[bot]" Date: Wed, 26 Aug 2026 01:28:34 +0000 Subject: [PATCH 2/7] ai: apply changes for #513 (2 review threads) Addresses: - #3858665815 at lib/kernel/KernelAuth.ts:840 - #3858665845 at CONNECTION_PARAMETERS.md:68 Signed-off-by: peco-engineer-bot[bot] Signed-off-by: eric-wang-1990 --- CONNECTION_PARAMETERS.md | 2 +- lib/kernel/KernelAuth.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CONNECTION_PARAMETERS.md b/CONNECTION_PARAMETERS.md index 33a18736..12a0a60b 100644 --- a/CONNECTION_PARAMETERS.md +++ b/CONNECTION_PARAMETERS.md @@ -65,7 +65,7 @@ column. | `oauthClientId` | `string` | ✅ | ✅ | `databricks-sql-connector` when absent | OAuth client id, used on **both** U2M and M2M. Forwarded verbatim on both backends when set. When absent it defaults to `databricks-sql-connector` — Thrift via `getClientId()` for both flows; kernel via `oauthClientId ?? DEFAULT_OAUTH_CLIENT_ID` on M2M, and by letting the napi binding apply its own (identical) default on U2M. **Parity:** `oauthClientId` + no secret routes to **U2M** with the id forwarded (flow selection keys off `oauthClientSecret` presence — see that row), so it does **not** throw an M2M "secret required" error. | | `oauthClientSecret` (M2M) | `string` | ✅ | ✅ | — | M2M client-credentials secret; its **presence** is the U2M-vs-M2M flow selector on both backends (`undefined` ⇒ U2M). Thrift → `DatabricksOAuth.clientSecret`. Kernel → native `oauthClientSecret` (workspace-OIDC M2M) or remapped to `azureClientSecret` (Entra-direct `AzureSpM2m`). A blank/reserved secret is forwarded verbatim and still selects M2M (Thrift parity) — except the Azure SP arm, which rejects it. | | `azureTenantId` / `useDatabricksOAuthInAzure` | `string` / `boolean` | ✅ | ⚠️ | — | **Honored on both.** By design the kernel routes **all U2M** (no secret, any cloud) to its cloud-blind in-house OAuth U2M flow — there is no Azure-specific U2M mode, so `useDatabricksOAuthInAzure` is inert on U2M and every Azure workspace (including `.databricks.azure.us` US-gov) is always supported, on any kernel build. `useDatabricksOAuthInAzure` selects only the **M2M** mechanism on an Azure host: absent/`false` → Entra-direct service-principal M2M (native `AzureSpM2m` mode, creds ride `oauthClientId`/`oauthClientSecret`, `azureTenantId` optional — kernel auto-discovers from the workspace `/aad/auth` redirect when omitted); `true` → workspace-OIDC M2M. (`lib/kernel/KernelAuth.ts` `buildKernelConnectionOptions`.) | -| `persistence` (custom OAuth token store) | `OAuthPersistence` | ✅ | ❌ | — | **Thrift-only.** Kernel throws; it auto-persists U2M tokens to `~/.config/databricks-sql-kernel/oauth/` and does not cache M2M. | +| `persistence` (custom OAuth token store) | `OAuthPersistence` | ✅ | ❌ | — | **Thrift-only.** Kernel throws (no custom-store hook). Its built-in U2M on-disk cache (`~/.config/databricks-sql-kernel/oauth/`) is **optional**, controlled by `tokenCacheEnabled` and **disabled by default** (`false`/omitted ⇒ in-memory only); it never caches M2M. See the `tokenCacheEnabled` row. | | `tokenCacheEnabled` (U2M on-disk cache) | `boolean` | ❌ | ✅ | `false` (disabled by default) | **Kernel U2M-only.** Controls the kernel's built-in on-disk token cache for U2M OAuth flows. When `true`, the refresh token is persisted (AES-256 encrypted) to `~/.config/databricks-sql-kernel/oauth/`. When `false` or omitted (the default), tokens remain in-memory only — silent-no-persist parity. No effect on M2M or other auth types. Distinct from the Thrift `persistence` custom-store hook. TODO: publish only after PR #283 (`tokenCacheEnabled` napi support) ships in `@databricks/databricks-sql-kernel`. | | `enableTokenFederation` / `federationClientId` | `boolean` / `string` | ✅ | ⚠️ | `false` / — | On the kernel backend these options apply only to `static-token`. Federation is always enabled, so `enableTokenFederation` is ignored; an omitted or empty client ID selects account-wide WIF and a non-empty ID selects SP-wide WIF. Thrift honors the boolean and also supports these options for `token-provider` and `external-token`. | diff --git a/lib/kernel/KernelAuth.ts b/lib/kernel/KernelAuth.ts index 6fb54cc5..5c5eff6c 100644 --- a/lib/kernel/KernelAuth.ts +++ b/lib/kernel/KernelAuth.ts @@ -822,8 +822,9 @@ export function buildKernelConnectionOptions(options: ConnectionOptions): Kernel throw new HiveDriverError( 'kernel backend: `persistence` (custom OAuth token store) is not yet wired through ' + 'to the kernel — requires `AuthConfig::External` plumbing. ' + - 'Today the kernel auto-persists U2M tokens to ' + - '`~/.config/databricks-sql-kernel/oauth/` which works for the standard flow; ' + + 'The kernel offers an optional built-in on-disk token cache at ' + + '`~/.config/databricks-sql-kernel/oauth/`, controlled by `tokenCacheEnabled` ' + + '(disabled by default); ' + "the JS-supplied hook (matching thrift's `OAuthPersistence` interface) lands " + 'when the kernel exposes it.', ); From e2c4585c3750759a6c8d8ac299e66b80890f3807 Mon Sep 17 00:00:00 2001 From: eric-wang-1990 Date: Tue, 25 Aug 2026 18:21:28 -0700 Subject: [PATCH 3/7] test(kernel): bump KERNEL_REV to kernel #283 and add U2M token-cache E2E Bumps KERNEL_REV 5e5dea9 -> 628abd6 (databricks-sql-kernel #283, "U2M token-cache control"; additive over #282 so the Azure SP surface is preserved) and regenerates the committed napi binding, which now exposes the `tokenCacheEnabled` / `tokenCachePassphrase` fields consumed by buildKernelConnectionOptions. The only index.js churn is napi-rs's embedded version guard (0.2.0 -> 0.2.1); the export set is unchanged. Replaces the placeholder skipped U2M e2e with a real interactive suite (gated behind DATABRICKS_KERNEL_U2M_INTERACTIVE so CI still skips it) that proves, end to end against a live workspace: - tokenCacheEnabled unset -> no on-disk token written (Thrift parity) - tokenCacheEnabled: true -> an AES-encrypted (non-JSON) token is persisted under dirs::config_dir()/ databricks-sql-kernel/oauth/ Verified: 2/2 e2e passing against an Azure pecotesting workspace; 322 kernel unit tests green against the rebuilt binding. Note: published optionalDependency pins remain 0.2.0 (no 0.2.1 kernel release yet) per the PR hold; local dev and the KERNEL_REV-built CI path use the freshly built .node. Co-authored-by: Isaac Signed-off-by: eric-wang-1990 --- KERNEL_REV | 2 +- native/kernel/index.d.ts | 14 +++ native/kernel/index.js | 108 ++++++++-------- tests/e2e/kernel/auth-u2m-e2e.test.ts | 173 +++++++++++++++++++++----- 4 files changed, 212 insertions(+), 85 deletions(-) diff --git a/KERNEL_REV b/KERNEL_REV index 322dd971..8d29e509 100644 --- a/KERNEL_REV +++ b/KERNEL_REV @@ -1 +1 @@ -ef1a6f2dbe72c66a9b6eefc4ca1ff31788f1efa9 +628abd6f5045897efcadb38ec77a1e9e0c23544e diff --git a/native/kernel/index.d.ts b/native/kernel/index.d.ts index 6fb596e2..9e33b65d 100644 --- a/native/kernel/index.d.ts +++ b/native/kernel/index.d.ts @@ -679,6 +679,20 @@ export interface ConnectionOptions { * authority for [`AuthMode::OAuthM2m`] / [`AuthMode::OAuthM2mJwt`]. */ tokenUrl?: string + /** + * U2M on-disk token-cache control (kernel path). Omitted ⇒ the kernel + * default (cache enabled, encrypted under a machine-local key, survives + * restarts). `false` disables on-disk persistence (re-login each fresh + * process); `true` keeps it enabled. Applies to [`AuthMode::OAuthU2m`]. + */ + tokenCacheEnabled?: boolean + /** + * Optional passphrase for the U2M on-disk token cache (AES-256 key). + * Omitted/blank ⇒ a machine-local derived key. Supplying one is stronger. + * A passphrase with no explicit `tokenCacheEnabled` implies enabled. + * Applies to [`AuthMode::OAuthU2m`]. + */ + tokenCachePassphrase?: string /** * Path to the PEM private-key file. Required for * [`AuthMode::OAuthM2mJwt`]. diff --git a/native/kernel/index.js b/native/kernel/index.js index 45ea1ec8..f33ca8b7 100644 --- a/native/kernel/index.js +++ b/native/kernel/index.js @@ -77,8 +77,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-android-arm64') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-android-arm64/package.json').version - if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -93,8 +93,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-android-arm-eabi') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-android-arm-eabi/package.json').version - if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -114,8 +114,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-win32-x64-gnu') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-win32-x64-gnu/package.json').version - if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -130,8 +130,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-win32-x64-msvc') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-win32-x64-msvc/package.json').version - if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -147,8 +147,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-win32-ia32-msvc') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-win32-ia32-msvc/package.json').version - if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -163,8 +163,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-win32-arm64-msvc') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-win32-arm64-msvc/package.json').version - if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -182,8 +182,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-darwin-universal') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-darwin-universal/package.json').version - if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -198,8 +198,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-darwin-x64') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-darwin-x64/package.json').version - if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -214,8 +214,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-darwin-arm64') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-darwin-arm64/package.json').version - if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -234,8 +234,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-freebsd-x64') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-freebsd-x64/package.json').version - if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -250,8 +250,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-freebsd-arm64') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-freebsd-arm64/package.json').version - if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -271,8 +271,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-linux-x64-musl') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-linux-x64-musl/package.json').version - if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -287,8 +287,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-linux-x64-gnu') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-linux-x64-gnu/package.json').version - if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -305,8 +305,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-linux-arm64-musl') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-linux-arm64-musl/package.json').version - if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -321,8 +321,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-linux-arm64-gnu') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-linux-arm64-gnu/package.json').version - if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -339,8 +339,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-linux-arm-musleabihf') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-linux-arm-musleabihf/package.json').version - if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -355,8 +355,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-linux-arm-gnueabihf') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-linux-arm-gnueabihf/package.json').version - if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -373,8 +373,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-linux-loong64-musl') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-linux-loong64-musl/package.json').version - if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -389,8 +389,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-linux-loong64-gnu') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-linux-loong64-gnu/package.json').version - if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -407,8 +407,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-linux-riscv64-musl') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-linux-riscv64-musl/package.json').version - if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -423,8 +423,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-linux-riscv64-gnu') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-linux-riscv64-gnu/package.json').version - if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -440,8 +440,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-linux-ppc64-gnu') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-linux-ppc64-gnu/package.json').version - if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -456,8 +456,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-linux-s390x-gnu') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-linux-s390x-gnu/package.json').version - if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -476,8 +476,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-openharmony-arm64') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-openharmony-arm64/package.json').version - if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -492,8 +492,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-openharmony-x64') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-openharmony-x64/package.json').version - if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -508,8 +508,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-openharmony-arm') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-openharmony-arm/package.json').version - if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -648,8 +648,8 @@ if (!nativeBinding || forceWasi) { if (!candidateFailed) { if (process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { const bindingPackageVersion = require('@databricks/databricks-sql-kernel-wasm32-wasi/package.json').version - if (bindingPackageVersion !== '0.2.0') { - throw new Error(`WASI binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.1') { + throw new Error(`WASI binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } } wasiBinding = require('@databricks/databricks-sql-kernel-wasm32-wasi') diff --git a/tests/e2e/kernel/auth-u2m-e2e.test.ts b/tests/e2e/kernel/auth-u2m-e2e.test.ts index 00287c10..3eb2b006 100644 --- a/tests/e2e/kernel/auth-u2m-e2e.test.ts +++ b/tests/e2e/kernel/auth-u2m-e2e.test.ts @@ -13,61 +13,174 @@ // limitations under the License. import { expect } from 'chai'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as nodePath from 'path'; import { DBSQLClient } from '../../../lib'; import { ConnectionOptions } from '../../../lib/contracts/IDBSQLClient'; import { InternalConnectionOptions } from '../../../lib/contracts/InternalConnectionOptions'; /** - * kernel-auth M1 OAuth U2M end-to-end — **SKIPPED pending browser harness**. + * kernel-auth OAuth U2M end-to-end, focused on the `tokenCacheEnabled` + * control (PR #513 / kernel #283). * - * U2M is interactive: the kernel opens a system browser - * (`auth/oauth/u2m.rs:414`, via the `open` crate), binds a local - * listener on port 8030 (via the JS adapter's hardcoded override), and - * waits up to 120s for the user to authenticate. + * U2M is **interactive**: the kernel opens a system browser, binds a local + * listener on port 8030 (via the JS adapter's hardcoded override), and waits + * for the user to complete the workspace login + consent. Because a human has + * to click through the browser, this suite cannot run unattended — it is + * therefore gated behind an explicit opt-in env var (`DATABRICKS_KERNEL_U2M_INTERACTIVE`) + * on top of the workspace host/path, so CI (which sets neither) skips it and + * never flaps. * - * Driving this from CI requires Playwright/Puppeteer to navigate the - * browser through the workspace login + consent screens. That harness - * is tracked as `TBD-oauth_u2m_test_harness` in testing-agent's - * findings; until it exists, this test stays `it.skip` so the e2e - * suite carries a slot for whoever lands the harness work. + * What it proves end-to-end, through + * DBSQLClient.connect({ useKernel: true, authType: 'databricks-oauth' }) + * → KernelBackend → napi binding → live workspace U2M browser flow: * - * The intended assertion sequence (mirrors `auth-m2m-e2e.test.ts`): - * 1. `client.connect({ useKernel: true, authType: 'databricks-oauth' })` - * — NO `oauthClientSecret` → kernel picks the U2M flow. - * 2. `openSession()` — kernel opens browser, waits for callback on - * localhost:8030, exchanges the auth code, returns Bearer token, - * issues the create-session request to kernel. - * 3. `session.close()` then `client.close()`. + * 1. **Disabled by default.** With `tokenCacheEnabled` unset, the connector + * passes `tokenCacheEnabled: false` to the kernel, so NO token is written + * to the on-disk cache — matching the Thrift backend's in-memory posture. + * 2. **Opt-in enable.** With `tokenCacheEnabled: true`, the kernel persists + * the U2M refresh token (AES-256 encrypted, not plaintext) to its on-disk + * cache under `dirs::config_dir()/databricks-sql-kernel/oauth/`. * - * Required env (gated additionally via `it.skip` until the harness - * lands, so absent env is a no-op today): + * Required env (suite skips unless ALL are set): * - DATABRICKS_PECOTESTING_SERVER_HOSTNAME * - DATABRICKS_PECOTESTING_HTTP_PATH - * - (no client_id/secret — U2M uses kernel default `databricks-cli`) + * - DATABRICKS_KERNEL_U2M_INTERACTIVE (any non-empty value — the human opt-in) + * + * Each case opens a fresh browser login (the cache dir is emptied between + * cases so a prior case's file can't produce a cache-hit that skips the + * browser). A human must complete both logins within the suite timeout. + */ + +/** + * The kernel stores U2M cache files at `dirs::config_dir()/databricks-sql-kernel/oauth/` + * (see `src/auth/oauth/cache.rs`). Mirror the Rust `dirs` crate's per-platform + * `config_dir()` here so the assertion checks the same directory the kernel + * writes to — on macOS that is `~/Library/Application Support`, NOT `~/.config` + * (the Linux path the docstrings mention). */ -describe('kernel-auth e2e — OAuth U2M through DBSQLClient ↔ KernelBackend ↔ napi binding', function suite() { +function kernelOAuthCacheDir(): string { + const home = os.homedir(); + if (process.platform === 'darwin') { + return nodePath.join(home, 'Library', 'Application Support', 'databricks-sql-kernel', 'oauth'); + } + if (process.platform === 'win32') { + const appData = process.env.APPDATA || nodePath.join(home, 'AppData', 'Roaming'); + return nodePath.join(appData, 'databricks-sql-kernel', 'oauth'); + } + const xdg = process.env.XDG_CONFIG_HOME || nodePath.join(home, '.config'); + return nodePath.join(xdg, 'databricks-sql-kernel', 'oauth'); +} + +/** Cache files are named `{sha256}.json` (`CacheKey::to_filename`). */ +function listCacheFiles(dir: string): string[] { + if (!fs.existsSync(dir)) { + return []; + } + return fs.readdirSync(dir).filter((f) => f.endsWith('.json')); +} + +describe('kernel-auth e2e — OAuth U2M token cache (interactive)', function suite() { + const host = process.env.DATABRICKS_PECOTESTING_SERVER_HOSTNAME; + const httpPath = process.env.DATABRICKS_PECOTESTING_HTTP_PATH; + const interactive = process.env.DATABRICKS_KERNEL_U2M_INTERACTIVE; + + const cacheDir = kernelOAuthCacheDir(); + let backupDir: string | undefined; + + // Interactive browser login + live warehouse round-trip; give the human time. this.timeout(300_000); - it.skip('[pending TBD-oauth_u2m_test_harness] interactive U2M round-trip', async () => { - const host = process.env.DATABRICKS_PECOTESTING_SERVER_HOSTNAME as string; - const path = process.env.DATABRICKS_PECOTESTING_HTTP_PATH as string; + before(function gate() { + if (!host || !httpPath || !interactive) { + // eslint-disable-next-line no-invalid-this + this.skip(); + } + // Never destroy a real user's cached tokens: move any existing cache + // aside for the duration of the suite and restore it in `after`. + if (fs.existsSync(cacheDir)) { + backupDir = `${cacheDir}.e2e-backup-${process.pid}`; + fs.renameSync(cacheDir, backupDir); + } + }); + + after(() => { + // Remove whatever the test wrote, then restore the user's originals. + if (fs.existsSync(cacheDir)) { + fs.rmSync(cacheDir, { recursive: true, force: true }); + } + if (backupDir && fs.existsSync(backupDir)) { + fs.renameSync(backupDir, cacheDir); + } + }); + beforeEach(() => { + // Start each case from an empty cache dir so a prior case's file cannot + // leak into this assertion — nor produce a cache-hit that skips the browser. + if (fs.existsSync(cacheDir)) { + fs.rmSync(cacheDir, { recursive: true, force: true }); + } + }); + + it('default (tokenCacheEnabled unset) persists NO token to disk', async () => { const client = new DBSQLClient(); + await client.connect({ + host: host as string, + path: httpPath as string, + authType: 'databricks-oauth', + useKernel: true, + } as ConnectionOptions & InternalConnectionOptions); - const connected = await client.connect({ - host, - path, + const session = await client.openSession(); + expect(session.id).to.be.a('string'); + + const operation = await session.executeStatement('SELECT 1 AS one'); + const rows = (await operation.fetchAll()) as Array>; + expect(Number(rows[0].one)).to.equal(1); + + await operation.close(); + await session.close(); + await client.close(); + + expect(listCacheFiles(cacheDir), 'no on-disk token cache when tokenCacheEnabled is unset').to.have.length(0); + }); + + it('tokenCacheEnabled:true persists an encrypted token to disk', async () => { + const client = new DBSQLClient(); + await client.connect({ + host: host as string, + path: httpPath as string, authType: 'databricks-oauth', useKernel: true, + tokenCacheEnabled: true, } as ConnectionOptions & InternalConnectionOptions); - expect(connected).to.equal(client); const session = await client.openSession(); expect(session.id).to.be.a('string'); - const status = await session.close(); - expect(status.isSuccess).to.equal(true); + const operation = await session.executeStatement('SELECT 1 AS one'); + const rows = (await operation.fetchAll()) as Array>; + expect(Number(rows[0].one)).to.equal(1); + await operation.close(); + await session.close(); await client.close(); + + const files = listCacheFiles(cacheDir); + expect(files, 'a token cache file is written when tokenCacheEnabled is true').to.have.length.greaterThan(0); + + // Encrypted at rest: the persisted file is AES-256 ciphertext, so it must + // NOT parse as the plaintext JSON token structure. + const raw = fs.readFileSync(nodePath.join(cacheDir, files[0])); + expect(raw.length, 'cache file is non-empty').to.be.greaterThan(0); + let parsedAsJson = false; + try { + JSON.parse(raw.toString('utf8')); + parsedAsJson = true; + } catch { + // expected — ciphertext is not valid UTF-8 JSON + } + expect(parsedAsJson, 'cache file must be encrypted, not plaintext JSON').to.equal(false); }); }); From 5ecd8023348e9451c579716648c37ae0e7b1bf5b Mon Sep 17 00:00:00 2001 From: eric-wang-1990 Date: Tue, 25 Aug 2026 22:10:36 -0700 Subject: [PATCH 4/7] style: prettier-format CONNECTION_PARAMETERS.md Normalizes trailing whitespace in two table rows so `prettier . --check` (the repo's lint gate) passes. No content change. Signed-off-by: eric-wang-1990 --- CONNECTION_PARAMETERS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONNECTION_PARAMETERS.md b/CONNECTION_PARAMETERS.md index 12a0a60b..6d30a651 100644 --- a/CONNECTION_PARAMETERS.md +++ b/CONNECTION_PARAMETERS.md @@ -65,8 +65,8 @@ column. | `oauthClientId` | `string` | ✅ | ✅ | `databricks-sql-connector` when absent | OAuth client id, used on **both** U2M and M2M. Forwarded verbatim on both backends when set. When absent it defaults to `databricks-sql-connector` — Thrift via `getClientId()` for both flows; kernel via `oauthClientId ?? DEFAULT_OAUTH_CLIENT_ID` on M2M, and by letting the napi binding apply its own (identical) default on U2M. **Parity:** `oauthClientId` + no secret routes to **U2M** with the id forwarded (flow selection keys off `oauthClientSecret` presence — see that row), so it does **not** throw an M2M "secret required" error. | | `oauthClientSecret` (M2M) | `string` | ✅ | ✅ | — | M2M client-credentials secret; its **presence** is the U2M-vs-M2M flow selector on both backends (`undefined` ⇒ U2M). Thrift → `DatabricksOAuth.clientSecret`. Kernel → native `oauthClientSecret` (workspace-OIDC M2M) or remapped to `azureClientSecret` (Entra-direct `AzureSpM2m`). A blank/reserved secret is forwarded verbatim and still selects M2M (Thrift parity) — except the Azure SP arm, which rejects it. | | `azureTenantId` / `useDatabricksOAuthInAzure` | `string` / `boolean` | ✅ | ⚠️ | — | **Honored on both.** By design the kernel routes **all U2M** (no secret, any cloud) to its cloud-blind in-house OAuth U2M flow — there is no Azure-specific U2M mode, so `useDatabricksOAuthInAzure` is inert on U2M and every Azure workspace (including `.databricks.azure.us` US-gov) is always supported, on any kernel build. `useDatabricksOAuthInAzure` selects only the **M2M** mechanism on an Azure host: absent/`false` → Entra-direct service-principal M2M (native `AzureSpM2m` mode, creds ride `oauthClientId`/`oauthClientSecret`, `azureTenantId` optional — kernel auto-discovers from the workspace `/aad/auth` redirect when omitted); `true` → workspace-OIDC M2M. (`lib/kernel/KernelAuth.ts` `buildKernelConnectionOptions`.) | -| `persistence` (custom OAuth token store) | `OAuthPersistence` | ✅ | ❌ | — | **Thrift-only.** Kernel throws (no custom-store hook). Its built-in U2M on-disk cache (`~/.config/databricks-sql-kernel/oauth/`) is **optional**, controlled by `tokenCacheEnabled` and **disabled by default** (`false`/omitted ⇒ in-memory only); it never caches M2M. See the `tokenCacheEnabled` row. | -| `tokenCacheEnabled` (U2M on-disk cache) | `boolean` | ❌ | ✅ | `false` (disabled by default) | **Kernel U2M-only.** Controls the kernel's built-in on-disk token cache for U2M OAuth flows. When `true`, the refresh token is persisted (AES-256 encrypted) to `~/.config/databricks-sql-kernel/oauth/`. When `false` or omitted (the default), tokens remain in-memory only — silent-no-persist parity. No effect on M2M or other auth types. Distinct from the Thrift `persistence` custom-store hook. TODO: publish only after PR #283 (`tokenCacheEnabled` napi support) ships in `@databricks/databricks-sql-kernel`. | +| `persistence` (custom OAuth token store) | `OAuthPersistence` | ✅ | ❌ | — | **Thrift-only.** Kernel throws (no custom-store hook). Its built-in U2M on-disk cache (`~/.config/databricks-sql-kernel/oauth/`) is **optional**, controlled by `tokenCacheEnabled` and **disabled by default** (`false`/omitted ⇒ in-memory only); it never caches M2M. See the `tokenCacheEnabled` row. | +| `tokenCacheEnabled` (U2M on-disk cache) | `boolean` | ❌ | ✅ | `false` (disabled by default) | **Kernel U2M-only.** Controls the kernel's built-in on-disk token cache for U2M OAuth flows. When `true`, the refresh token is persisted (AES-256 encrypted) to `~/.config/databricks-sql-kernel/oauth/`. When `false` or omitted (the default), tokens remain in-memory only — silent-no-persist parity. No effect on M2M or other auth types. Distinct from the Thrift `persistence` custom-store hook. TODO: publish only after PR #283 (`tokenCacheEnabled` napi support) ships in `@databricks/databricks-sql-kernel`. | | `enableTokenFederation` / `federationClientId` | `boolean` / `string` | ✅ | ⚠️ | `false` / — | On the kernel backend these options apply only to `static-token`. Federation is always enabled, so `enableTokenFederation` is ignored; an omitted or empty client ID selects account-wide WIF and a non-empty ID selects SP-wide WIF. Thrift honors the boolean and also supports these options for `token-provider` and `external-token`. | ## HTTP client, proxy, retries From a2013a94425409bc95684beca94c40fa15168870 Mon Sep 17 00:00:00 2001 From: eric-wang-1990 Date: Tue, 25 Aug 2026 23:56:15 -0700 Subject: [PATCH 5/7] fix(kernel): keep napi version guard at 0.2.0 until 0.2.1 npm release The regenerated native/kernel/index.js baked the expected binding version to 0.2.1, but package.json optionalDependencies still pin all 8 kernel packages to 0.2.0 (no 0.2.1 npm release has shipped). The guard is gated on NAPI_RS_ENFORCE_VERSION_CHECK, so default consumers are unaffected, but any environment that sets the flag would hit "expected 0.2.1 but got 0.2.0" and fail to load. Keep the version literal at 0.2.0 so the loader stays in lockstep with the pinned binding. Bump both the guard literals and the optionalDependencies pins to 0.2.1 together in the follow-up commit once 0.2.1 publishes. Co-authored-by: Isaac Signed-off-by: eric-wang-1990 --- native/kernel/index.js | 108 ++++++++++++++++++++--------------------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/native/kernel/index.js b/native/kernel/index.js index f33ca8b7..45ea1ec8 100644 --- a/native/kernel/index.js +++ b/native/kernel/index.js @@ -77,8 +77,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-android-arm64') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-android-arm64/package.json').version - if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -93,8 +93,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-android-arm-eabi') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-android-arm-eabi/package.json').version - if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -114,8 +114,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-win32-x64-gnu') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-win32-x64-gnu/package.json').version - if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -130,8 +130,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-win32-x64-msvc') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-win32-x64-msvc/package.json').version - if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -147,8 +147,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-win32-ia32-msvc') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-win32-ia32-msvc/package.json').version - if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -163,8 +163,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-win32-arm64-msvc') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-win32-arm64-msvc/package.json').version - if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -182,8 +182,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-darwin-universal') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-darwin-universal/package.json').version - if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -198,8 +198,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-darwin-x64') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-darwin-x64/package.json').version - if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -214,8 +214,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-darwin-arm64') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-darwin-arm64/package.json').version - if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -234,8 +234,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-freebsd-x64') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-freebsd-x64/package.json').version - if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -250,8 +250,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-freebsd-arm64') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-freebsd-arm64/package.json').version - if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -271,8 +271,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-linux-x64-musl') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-linux-x64-musl/package.json').version - if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -287,8 +287,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-linux-x64-gnu') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-linux-x64-gnu/package.json').version - if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -305,8 +305,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-linux-arm64-musl') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-linux-arm64-musl/package.json').version - if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -321,8 +321,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-linux-arm64-gnu') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-linux-arm64-gnu/package.json').version - if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -339,8 +339,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-linux-arm-musleabihf') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-linux-arm-musleabihf/package.json').version - if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -355,8 +355,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-linux-arm-gnueabihf') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-linux-arm-gnueabihf/package.json').version - if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -373,8 +373,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-linux-loong64-musl') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-linux-loong64-musl/package.json').version - if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -389,8 +389,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-linux-loong64-gnu') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-linux-loong64-gnu/package.json').version - if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -407,8 +407,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-linux-riscv64-musl') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-linux-riscv64-musl/package.json').version - if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -423,8 +423,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-linux-riscv64-gnu') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-linux-riscv64-gnu/package.json').version - if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -440,8 +440,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-linux-ppc64-gnu') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-linux-ppc64-gnu/package.json').version - if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -456,8 +456,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-linux-s390x-gnu') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-linux-s390x-gnu/package.json').version - if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -476,8 +476,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-openharmony-arm64') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-openharmony-arm64/package.json').version - if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -492,8 +492,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-openharmony-x64') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-openharmony-x64/package.json').version - if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -508,8 +508,8 @@ function requireNative() { try { const binding = require('@databricks/databricks-sql-kernel-openharmony-arm') const bindingPackageVersion = require('@databricks/databricks-sql-kernel-openharmony-arm/package.json').version - if (bindingPackageVersion !== '0.2.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -648,8 +648,8 @@ if (!nativeBinding || forceWasi) { if (!candidateFailed) { if (process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { const bindingPackageVersion = require('@databricks/databricks-sql-kernel-wasm32-wasi/package.json').version - if (bindingPackageVersion !== '0.2.1') { - throw new Error(`WASI binding package version mismatch, expected 0.2.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0') { + throw new Error(`WASI binding package version mismatch, expected 0.2.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } } wasiBinding = require('@databricks/databricks-sql-kernel-wasm32-wasi') From e4c92daafb073d1199eb0c3f0caf7820bf8432f3 Mon Sep 17 00:00:00 2001 From: eric-wang-1990 Date: Wed, 26 Aug 2026 00:02:12 -0700 Subject: [PATCH 6/7] chore(kernel): drop unused tokenCachePassphrase from napi index.d.ts The connector wires only tokenCacheEnabled through to the kernel U2M path; the passphrase surface is intentionally out of scope for this PR (enable flag only). Remove the tokenCachePassphrase type entry so the committed napi surface matches what the connector actually forwards. It can be re-added alongside real wiring when the passphrase option is surfaced. Co-authored-by: Isaac Signed-off-by: eric-wang-1990 --- native/kernel/index.d.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/native/kernel/index.d.ts b/native/kernel/index.d.ts index 9e33b65d..e85eb2c6 100644 --- a/native/kernel/index.d.ts +++ b/native/kernel/index.d.ts @@ -686,13 +686,6 @@ export interface ConnectionOptions { * process); `true` keeps it enabled. Applies to [`AuthMode::OAuthU2m`]. */ tokenCacheEnabled?: boolean - /** - * Optional passphrase for the U2M on-disk token cache (AES-256 key). - * Omitted/blank ⇒ a machine-local derived key. Supplying one is stronger. - * A passphrase with no explicit `tokenCacheEnabled` implies enabled. - * Applies to [`AuthMode::OAuthU2m`]. - */ - tokenCachePassphrase?: string /** * Path to the PEM private-key file. Required for * [`AuthMode::OAuthM2mJwt`]. From f3f9eafdc73cdcc789de52c506a33bbe0f2e5dfc Mon Sep 17 00:00:00 2001 From: "peco-engineer-bot[bot]" Date: Wed, 26 Aug 2026 07:05:32 +0000 Subject: [PATCH 7/7] ai: apply changes for #513 (1 review thread) Addresses: - #3860354328 at tests/e2e/kernel/auth-u2m-e2e.test.ts:111 Signed-off-by: peco-engineer-bot[bot] --- tests/e2e/kernel/auth-u2m-e2e.test.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/e2e/kernel/auth-u2m-e2e.test.ts b/tests/e2e/kernel/auth-u2m-e2e.test.ts index 3eb2b006..4bbc5577 100644 --- a/tests/e2e/kernel/auth-u2m-e2e.test.ts +++ b/tests/e2e/kernel/auth-u2m-e2e.test.ts @@ -88,6 +88,10 @@ describe('kernel-auth e2e — OAuth U2M token cache (interactive)', function sui const cacheDir = kernelOAuthCacheDir(); let backupDir: string | undefined; + // Only true once the gate has passed AND the backup has been taken. The + // `after` hook keys its destructive cleanup off this so a skipped run (gate + // env vars absent) never touches the developer's real on-disk cache. + let suiteActive = false; // Interactive browser login + live warehouse round-trip; give the human time. this.timeout(300_000); @@ -103,9 +107,14 @@ describe('kernel-auth e2e — OAuth U2M token cache (interactive)', function sui backupDir = `${cacheDir}.e2e-backup-${process.pid}`; fs.renameSync(cacheDir, backupDir); } + suiteActive = true; }); after(() => { + // A skipped run took no backup — leave the developer's cache untouched. + if (!suiteActive) { + return; + } // Remove whatever the test wrote, then restore the user's originals. if (fs.existsSync(cacheDir)) { fs.rmSync(cacheDir, { recursive: true, force: true });