Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 11 additions & 12 deletions CONNECTION_PARAMETERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,16 @@ column.

## Authentication

| Option | Type | Thrift | Kernel | Default Value | Note |
| ---------------------------------------------- | -------------------------------------------------------------------------- | :------: | :------: | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `authType` — supported on both backends | `'access-token'` \| `'databricks-oauth'` | ✅ | ✅ | `'access-token'` | The two auth modes both backends accept. `access-token` uses `token` (PAT) and is the default when `authType` is omitted. `databricks-oauth` covers M2M (`oauthClientId` + `oauthClientSecret`; kernel runs OIDC discovery + client-credentials internally) and U2M (browser; no secret — kernel U2M differs slightly, see the OAuth sub-option rows below). |
| `authType` — Thrift-only | `'custom'` \| `'token-provider'` \| `'external-token'` \| `'static-token'` | ✅ | ❌ | — | **Thrift-only.** `custom` (`provider: IAuthentication`), `token-provider` (`tokenProvider: ITokenProvider`), `external-token` (`getToken: TokenCallback`), `static-token` (`staticToken`). The kernel throws `unsupported auth mode` for all four — it supports only the two modes above. |
| `oauthScopes` | `Array<string>` | ❌ | ✅ | U2M `['sql','offline_access']`, M2M `['all-apis']` | **Thrift ignores `oauthScopes`** — `createAuthProvider` never threads it into `DatabricksOAuth`, so `authenticate()` always falls back to `defaultOAuthScopes` (`['sql','offline_access']`). Only the kernel honors a custom `oauthScopes`; its defaults happen to match Thrift's fallback. |
| `oauthClientId` (U2M) | `string` | ✅ | ✅ | napi default `client_id` when absent | The kernel adapter (`buildKernelConnectionOptions`) forwards a custom `oauthClientId` verbatim on the U2M arm; when it is absent the napi binding applies its own default `client_id`. Whether the native binding then honors or rejects a custom id is not observable from this repo — the TypeScript layer neither hardcodes an id nor rejects one. |
| `oauthClientId` + no secret | `string` | ✅ (U2M) | ✅ (U2M) | — | **Parity.** The kernel keys flow selection off `oauthClientSecret` presence exactly like Thrift, so `oauthClientId` + no secret routes to **U2M** (with the id forwarded) — it does **not** throw an M2M "secret required" error. |
| `azureTenantId` / `useDatabricksOAuthInAzure` | `string` / `boolean` | ✅ | ❌ | — | **Thrift-only.** Kernel rejects Azure-direct (Entra) OAuth; workspace-OIDC discovery covers Azure workspaces without it. |
| `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. |
| `enableTokenFederation` / `federationClientId` | `boolean` / `string` | ✅ | | `false` / — | **Thrift-only** (available on the token-provider / external-token / static-token arms, none of which the kernel supports). |
| Option | Type | Thrift | Kernel | Default Value | Note |
| ---------------------------------------------- | ------------------------------------------------------------ | :------: | :------: | -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `authType` — supported on both backends | `'access-token'` \| `'databricks-oauth'` \| `'static-token'` | ✅ | ✅ | `'access-token'` | `access-token` uses `token` (PAT) and is the default when `authType` is omitted. `static-token` uses `staticToken`; the kernel maps it to its native bearer-token mode. `databricks-oauth` covers M2M (`oauthClientId` + `oauthClientSecret`) and U2M (browser; no secret). |
| `authType` — Thrift-only | `'custom'` \| `'token-provider'` \| `'external-token'` | ✅ | ❌ | — | **Thrift-only.** `custom` uses `provider: IAuthentication`, `token-provider` uses `tokenProvider: ITokenProvider`, and `external-token` uses `getToken: TokenCallback`. The kernel throws `unsupported auth mode` for these modes. |
| `oauthScopes` | `Array<string>` | ❌ | ✅ | U2M `['sql','offline_access']`, M2M `['all-apis']` | **Thrift ignores `oauthScopes`** — `createAuthProvider` never threads it into `DatabricksOAuth`, so `authenticate()` always falls back to `defaultOAuthScopes` (`['sql','offline_access']`). Only the kernel honors a custom `oauthScopes`; its defaults happen to match Thrift's fallback. |
| `oauthClientId` (U2M) | `string` | ✅ | ✅ | napi default `client_id` when absent | The kernel adapter (`buildKernelConnectionOptions`) forwards a custom `oauthClientId` verbatim on the U2M arm; when it is absent the napi binding applies its own default `client_id`. Whether the native binding then honors or rejects a custom id is not observable from this repo — the TypeScript layer neither hardcodes an id nor rejects one. |
| `oauthClientId` + no secret | `string` | ✅ (U2M) | ✅ (U2M) | — | **Parity.** The kernel keys flow selection off `oauthClientSecret` presence exactly like Thrift, so `oauthClientId` + no secret routes to **U2M** (with the id forwarded) — it does **not** throw an M2M "secret required" error. |
| `azureTenantId` / `useDatabricksOAuthInAzure` | `string` / `boolean` | ✅ | ❌ | — | **Thrift-only.** Kernel rejects Azure-direct (Entra) OAuth; workspace-OIDC discovery covers Azure workspaces without it. |
| `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. |
| `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

Expand Down Expand Up @@ -161,8 +161,7 @@ backend, so they are read regardless of `useKernel`. Defaults are sourced from
1. `enableMetricViewMetadata` — auto-injected for both backends in
`DBSQLClient.openSession`, but the conf key is likely dropped by the
kernel's session-conf allowlist, so it has no effect on the kernel path.
2. Auth types `custom`, `token-provider`, `external-token`, `static-token`,
plus `enableTokenFederation` / `federationClientId`.
2. Auth types `custom`, `token-provider`, and `external-token`.
3. `azureTenantId` / `useDatabricksOAuthInAzure` (Azure-direct OAuth).
4. `persistence` (custom OAuth token store).
5. SOCKS proxies.
Expand Down
2 changes: 1 addition & 1 deletion KERNEL_REV
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0d46716c466897148dfc1d2976ff03bdf097998c
eff8950428f4e6cc9975c663ec919f334962f7d0
2 changes: 2 additions & 0 deletions lib/contracts/IDBSQLClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ type AuthOptions =
| {
authType: 'static-token';
staticToken: string;
/** Ignored by the kernel backend, where token federation is always enabled. */
enableTokenFederation?: boolean;
/** Selects SP-wide federation; omitted selects account-wide federation. */
federationClientId?: string;
};

Expand Down
42 changes: 40 additions & 2 deletions lib/kernel/KernelAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ const DEFAULT_OAUTH_CLIENT_ID = 'databricks-sql-connector';
* everything else (client_id, scopes, callback timeout,
* token_url_override) uses kernel defaults.
*
* `static-token` reuses the native PAT bearer-token mode, where federation is
* always enabled. `enableTokenFederation` is ignored; a non-empty
* `federationClientId` selects SP-wide WIF and omission selects account-wide.
*
* The `authMode` string literals MUST match the napi-emitted `AuthMode`
* variant names verbatim (`'Pat'`, `'OAuthM2m'`, `'OAuthU2m'` — napi-rs's
* `#[napi(string_enum)]` without an explicit case option emits the
Expand Down Expand Up @@ -212,10 +216,19 @@ export interface KernelProxyOptions {
};
}

export interface KernelFederationOptions {
/**
* SP-wide Workload Identity Federation client id. Omitted selects BYOT /
* account-wide WIF.
*/
identityFederationClientId?: string;
}

export type KernelNativeConnectionOptions = KernelSessionDefaults &
KernelTlsOptions &
KernelHttpOptions &
KernelProxyOptions &
KernelFederationOptions &
(
| {
hostName: string;
Expand Down Expand Up @@ -443,6 +456,10 @@ export function buildKernelHttpOptions(options: ConnectionOptions): KernelHttpOp
* - PAT: `authType: 'access-token'` (or undefined, which already means
* PAT throughout the existing driver — see
* `DBSQLClient.createAuthProvider`).
* - Static token: `authType: 'static-token'` + `staticToken`. The token is
* forwarded through the native PAT bearer-token mode, where federation is
* always enabled. `federationClientId` selects SP-wide WIF; omission
* selects account-wide WIF. `enableTokenFederation` is ignored.
* - OAuth M2M: `authType: 'databricks-oauth'` + `oauthClientId` +
* `oauthClientSecret`. Kernel handles OIDC discovery, client_credentials
* exchange, and re-auth on expiry internally.
Expand Down Expand Up @@ -556,7 +573,8 @@ export function buildKernelConnectionOptions(options: ConnectionOptions): Kernel
maxConnections?: number;
} & KernelTlsOptions &
KernelHttpOptions &
KernelProxyOptions = {
KernelProxyOptions &
KernelFederationOptions = {
hostName: options.host,
httpPath: prependSlash(options.path),
// Match the NodeJS Thrift driver, which surfaces INTERVAL columns as
Expand Down Expand Up @@ -621,6 +639,26 @@ export function buildKernelConnectionOptions(options: ConnectionOptions): Kernel
return { ...base, authMode: 'Pat', token };
}

if (authType === 'static-token') {
const { staticToken, federationClientId } = options as {
staticToken?: string;
federationClientId?: string;
};
if (typeof staticToken !== 'string' || isBlankOrReserved(staticToken)) {
throw new AuthenticationError(
"kernel backend: a non-empty token must be supplied via `staticToken` when using `authType: 'static-token'`.",
);
}
if (oauth.oauthClientId !== undefined || oauth.oauthClientSecret !== undefined) {
Comment thread
vuanhphung marked this conversation as resolved.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Low — The static-token branch guards against conflicting OAuth credentials (oauthClientId/oauthClientSecret) but does not guard against a conflicting token (PAT). The other two branches are symmetric about ambiguity: access-token rejects when OAuth fields are also set, and databricks-oauth rejects when token is also set. Here, if a caller supplies both authType: 'static-token' + staticToken and a token, the token is silently dropped and static-token auth is used with no diagnostic.

This matters most for a user migrating from PAT who leaves token in their config while switching authType to static-token — a likely misconfiguration that the symmetric guard elsewhere is designed to surface. Consider rejecting token alongside staticToken for parity with the other arms.

throw new HiveDriverError(
'kernel backend: cannot supply `staticToken` alongside `oauthClientId`/`oauthClientSecret` ' +
'on the same connection. Pick one auth mode.',
);
}
base.identityFederationClientId = federationClientId || undefined;
return { ...base, authMode: 'Pat', token: staticToken };
}

if (authType === 'databricks-oauth') {
if ((options as { token?: string }).token !== undefined) {
throw new HiveDriverError(
Expand Down Expand Up @@ -694,7 +732,7 @@ export function buildKernelConnectionOptions(options: ConnectionOptions): Kernel

throw new HiveDriverError(
`kernel backend: unsupported auth mode '${authType}'. ` +
"Supported modes on the kernel backend today: 'access-token' (PAT) and 'databricks-oauth' " +
"Supported modes on the kernel backend today: 'access-token' (PAT), 'static-token', and 'databricks-oauth' " +
'(M2M with oauthClientId+oauthClientSecret, or U2M with neither).',
);
}
11 changes: 5 additions & 6 deletions lib/kernel/KernelBackend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ export interface KernelBackendOptions {
* kernel-backed implementation of `IBackend`.
*
* **M0 dispatch model:** the napi binding's `openSession()` already
* builds a kernel `Session` from PAT + hostname + httpPath, so there is
* builds a kernel `Session` from auth options + hostname + httpPath, so there is
* no "connect" round-trip before `openSession` — `connect()` only
* captures the `ConnectionOptions` and validates that PAT auth is in
* use. The actual session open happens inside `openSession()`.
* captures and validates the `ConnectionOptions`. The actual session open
* happens inside `openSession()`.
*
* **Auth validation:** delegates to `buildKernelConnectionOptions` from
* `KernelAuth`, which mirrors the existing DBSQLClient validation pattern
Expand Down Expand Up @@ -84,9 +84,8 @@ export default class KernelBackend implements IBackend {
}

public async connect(options: ConnectionOptions): Promise<void> {
// Validate PAT auth + capture the napi-binding option shape.
// Any non-PAT mode (or a missing/empty token) throws here, before
// we ever touch the native binding.
// Validate auth + capture the napi-binding option shape before touching
// the native binding.
// Forward the driver's retry config to the kernel, which owns the retry
// loop on the kernel path. This keeps kernel and Thrift governed by one retry
// config (the same `ClientConfig` knobs the Thrift `HttpRetryPolicy` reads),
Expand Down
11 changes: 8 additions & 3 deletions native/kernel/index.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions tests/unit/kernel/auth-pat.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,8 @@ describe('KernelAuth — PAT auth options builder', () => {
);
});

it('rejects external-token, static-token, and custom auth modes', () => {
const authTypes = ['external-token', 'static-token', 'custom'] as const;
it('rejects external-token and custom auth modes', () => {
const authTypes = ['external-token', 'custom'] as const;
for (const authType of authTypes) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const opts = {
Expand Down
Loading
Loading