@lightdash/cli 0.2179.1 → 0.2180.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/dbt/targets/Databricks/oauth.d.ts +16 -0
- package/dist/dbt/targets/Databricks/oauth.d.ts.map +1 -0
- package/dist/dbt/targets/Databricks/oauth.js +156 -0
- package/dist/dbt/targets/databricks.d.ts +2 -0
- package/dist/dbt/targets/databricks.d.ts.map +1 -1
- package/dist/dbt/targets/databricks.js +31 -5
- package/dist/handlers/dbt/getWarehouseClient.d.ts.map +1 -1
- package/dist/handlers/dbt/getWarehouseClient.js +27 -3
- package/dist/handlers/login/oauth.d.ts +2 -0
- package/dist/handlers/login/oauth.d.ts.map +1 -0
- package/dist/handlers/login/oauth.js +27 -0
- package/dist/handlers/oauthLogin.d.ts.map +1 -1
- package/dist/handlers/oauthLogin.js +2 -22
- package/package.json +3 -3
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Databricks OAuth tokens result
|
|
3
|
+
*/
|
|
4
|
+
export interface DatabricksOAuthTokens {
|
|
5
|
+
accessToken: string;
|
|
6
|
+
refreshToken: string;
|
|
7
|
+
expiresAt: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Perform Databricks U2M OAuth flow
|
|
11
|
+
* Opens browser for user authentication and exchanges authorization code for tokens
|
|
12
|
+
* @param host Databricks workspace host
|
|
13
|
+
* @param clientId OAuth client ID (defaults to 'databricks-cli')
|
|
14
|
+
*/
|
|
15
|
+
export declare const performDatabricksOAuthFlow: (host: string, clientId: string, clientSecret: string | undefined) => Promise<DatabricksOAuthTokens>;
|
|
16
|
+
//# sourceMappingURL=oauth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../../../../src/dbt/targets/Databricks/oauth.ts"],"names":[],"mappings":"AAQA;;GAEG;AACH,MAAM,WAAW,qBAAqB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACrB;AAED;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,SAC7B,MAAM,YACF,MAAM,gBACF,MAAM,GAAG,SAAS,KACjC,OAAO,CAAC,qBAAqB,CAuM/B,CAAC"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.performDatabricksOAuthFlow = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const common_1 = require("@lightdash/common");
|
|
6
|
+
const http = tslib_1.__importStar(require("http"));
|
|
7
|
+
const node_fetch_1 = tslib_1.__importDefault(require("node-fetch"));
|
|
8
|
+
const openid_client_1 = require("openid-client");
|
|
9
|
+
const url_1 = require("url");
|
|
10
|
+
const globalState_1 = tslib_1.__importDefault(require("../../../globalState"));
|
|
11
|
+
const oauth_1 = require("../../../handlers/login/oauth");
|
|
12
|
+
/**
|
|
13
|
+
* Perform Databricks U2M OAuth flow
|
|
14
|
+
* Opens browser for user authentication and exchanges authorization code for tokens
|
|
15
|
+
* @param host Databricks workspace host
|
|
16
|
+
* @param clientId OAuth client ID (defaults to 'databricks-cli')
|
|
17
|
+
*/
|
|
18
|
+
const performDatabricksOAuthFlow = async (host, clientId, clientSecret) => {
|
|
19
|
+
// Create a promise that will be resolved when we get the authorization code
|
|
20
|
+
let resolveAuth;
|
|
21
|
+
let rejectAuth;
|
|
22
|
+
const authPromise = new Promise((resolve, reject) => {
|
|
23
|
+
resolveAuth = resolve;
|
|
24
|
+
rejectAuth = reject;
|
|
25
|
+
});
|
|
26
|
+
let port = 0;
|
|
27
|
+
// Generate PKCE values
|
|
28
|
+
const codeVerifier = openid_client_1.generators.codeVerifier();
|
|
29
|
+
const codeChallenge = openid_client_1.generators.codeChallenge(codeVerifier);
|
|
30
|
+
const state = openid_client_1.generators.state();
|
|
31
|
+
// Create HTTP server to handle the callback at root path
|
|
32
|
+
const server = http.createServer((req, res) => {
|
|
33
|
+
const callbackUrl = new url_1.URL(req.url || '/', `http://localhost:${port}`);
|
|
34
|
+
const code = callbackUrl.searchParams.get('code');
|
|
35
|
+
const returnedState = callbackUrl.searchParams.get('state');
|
|
36
|
+
const error = callbackUrl.searchParams.get('error');
|
|
37
|
+
res.setHeader('Content-Type', 'text/html');
|
|
38
|
+
if (error === 'access_denied') {
|
|
39
|
+
rejectAuth(new common_1.AuthorizationError(`OAuth error: access denied`));
|
|
40
|
+
res.writeHead(400);
|
|
41
|
+
res.end('<html><body><h1>Authentication Failed</h1><p>Access denied. You can close this window.</p></body></html>');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (error) {
|
|
45
|
+
rejectAuth(new common_1.AuthorizationError(`OAuth error: ${error}`));
|
|
46
|
+
res.writeHead(400);
|
|
47
|
+
res.end(`<html><body><h1>Authentication Failed</h1><p>Error: ${error}</p></body></html>`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (!code || !returnedState) {
|
|
51
|
+
rejectAuth(new common_1.AuthorizationError('Missing authorization code or state'));
|
|
52
|
+
res.writeHead(400);
|
|
53
|
+
res.end('<html><body><h1>Authentication Failed</h1><p>Missing authorization code or state.</p></body></html>');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (returnedState !== state) {
|
|
57
|
+
rejectAuth(new common_1.AuthorizationError('Authentication session expired or invalid'));
|
|
58
|
+
res.writeHead(400);
|
|
59
|
+
res.end('<html><body><h1>Authentication Failed</h1><p>Session expired or invalid. Please close this window and try again.</p></body></html>');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
// Success - resolve the promise
|
|
63
|
+
resolveAuth({ code, state: returnedState });
|
|
64
|
+
res.writeHead(200);
|
|
65
|
+
res.end('<html><body><h1>Authentication Successful</h1><p>You can close this window and return to the CLI.</p></body></html>');
|
|
66
|
+
});
|
|
67
|
+
// Start the server on port 8020 (standard for Databricks CLI)
|
|
68
|
+
const preferredPort = 8020;
|
|
69
|
+
await new Promise((resolve, reject) => {
|
|
70
|
+
server.on('error', (err) => {
|
|
71
|
+
// perhaps 8020 port is busy, but we can't use a random port
|
|
72
|
+
reject(err);
|
|
73
|
+
});
|
|
74
|
+
server.listen(preferredPort, () => {
|
|
75
|
+
const address = server.address();
|
|
76
|
+
if (address === null)
|
|
77
|
+
throw new Error('Failed to get server address');
|
|
78
|
+
if (typeof address === 'object') {
|
|
79
|
+
port = address.port;
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
port = parseInt(address.toString(), 10);
|
|
83
|
+
}
|
|
84
|
+
globalState_1.default.debug(`> OAuth callback server listening on port ${port}`);
|
|
85
|
+
resolve();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
const redirectUri = `http://localhost:${port}`;
|
|
89
|
+
globalState_1.default.debug(`> Starting CLI callback server on URI: ${redirectUri}`);
|
|
90
|
+
try {
|
|
91
|
+
// Build Databricks authorization URL
|
|
92
|
+
const authUrl = new url_1.URL('/oidc/v1/authorize', `https://${host}`);
|
|
93
|
+
authUrl.searchParams.set('client_id', clientId);
|
|
94
|
+
authUrl.searchParams.set('redirect_uri', redirectUri);
|
|
95
|
+
authUrl.searchParams.set('response_type', 'code');
|
|
96
|
+
authUrl.searchParams.set('scope', 'all-apis offline_access');
|
|
97
|
+
authUrl.searchParams.set('code_challenge', codeChallenge);
|
|
98
|
+
authUrl.searchParams.set('code_challenge_method', 'S256');
|
|
99
|
+
authUrl.searchParams.set('state', state);
|
|
100
|
+
console.error(`\n🔐 Databricks Authentication`);
|
|
101
|
+
console.error(`Opening browser for authentication...`);
|
|
102
|
+
console.error(`If the browser doesn't open automatically, please visit:`);
|
|
103
|
+
console.error(`${authUrl.href}\n`);
|
|
104
|
+
// Try to open the browser
|
|
105
|
+
await (0, oauth_1.openBrowser)(authUrl.href);
|
|
106
|
+
// Wait for the authorization code
|
|
107
|
+
const { code } = await authPromise;
|
|
108
|
+
globalState_1.default.debug(`> Got authorization code ${code.substring(0, 10)}...`);
|
|
109
|
+
// Exchange the authorization code for tokens
|
|
110
|
+
const tokenUrl = new url_1.URL('/oidc/v1/token', `https://${host}`);
|
|
111
|
+
// Build token request parameters
|
|
112
|
+
const tokenParams = {
|
|
113
|
+
grant_type: 'authorization_code',
|
|
114
|
+
code,
|
|
115
|
+
client_id: clientId,
|
|
116
|
+
redirect_uri: redirectUri,
|
|
117
|
+
code_verifier: codeVerifier,
|
|
118
|
+
};
|
|
119
|
+
// For confidential clients (custom OAuth apps), include client_secret
|
|
120
|
+
if (clientSecret) {
|
|
121
|
+
tokenParams.client_secret = clientSecret;
|
|
122
|
+
}
|
|
123
|
+
const tokenResponse = await (0, node_fetch_1.default)(tokenUrl.href, {
|
|
124
|
+
method: 'POST',
|
|
125
|
+
headers: {
|
|
126
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
127
|
+
},
|
|
128
|
+
body: new URLSearchParams(tokenParams),
|
|
129
|
+
});
|
|
130
|
+
if (!tokenResponse.ok) {
|
|
131
|
+
const errorText = await tokenResponse.text();
|
|
132
|
+
throw new common_1.AuthorizationError(`Token exchange failed: ${tokenResponse.status} ${errorText}`);
|
|
133
|
+
}
|
|
134
|
+
const tokenData = (await tokenResponse.json());
|
|
135
|
+
const accessToken = tokenData.access_token;
|
|
136
|
+
const refreshToken = tokenData.refresh_token;
|
|
137
|
+
const expiresIn = tokenData.expires_in;
|
|
138
|
+
if (!accessToken || !refreshToken) {
|
|
139
|
+
throw new common_1.AuthorizationError('No access token or refresh token received from Databricks');
|
|
140
|
+
}
|
|
141
|
+
globalState_1.default.debug(`> OAuth access token: ${accessToken.substring(0, 10)}...`);
|
|
142
|
+
globalState_1.default.debug(`> OAuth refresh token: ${refreshToken.substring(0, 10)}...`);
|
|
143
|
+
// Calculate expiration timestamp (current time + expires_in)
|
|
144
|
+
const expiresAt = Math.floor(Date.now() / 1000) + expiresIn;
|
|
145
|
+
return {
|
|
146
|
+
accessToken,
|
|
147
|
+
refreshToken,
|
|
148
|
+
expiresAt,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
finally {
|
|
152
|
+
// Clean up the server
|
|
153
|
+
server.close();
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
exports.performDatabricksOAuthFlow = performDatabricksOAuthFlow;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"databricks.d.ts","sourceRoot":"","sources":["../../../src/dbt/targets/databricks.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,2BAA2B,EAI9B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAE,MAAM,KAAK,CAAC;AAGrC,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAElC,KAAK,uBAAuB,GAAG;IAC3B,CAAC,IAAI,EAAE,MAAM,GAAG;QACZ,SAAS,EAAE,MAAM,CAAC;KACrB,CAAC;CACL,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC3B,IAAI,EAAE,YAAY,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAElB,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,SAAS,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"databricks.d.ts","sourceRoot":"","sources":["../../../src/dbt/targets/databricks.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,2BAA2B,EAI9B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAE,MAAM,KAAK,CAAC;AAGrC,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAElC,KAAK,uBAAuB,GAAG;IAC3B,CAAC,IAAI,EAAE,MAAM,GAAG;QACZ,SAAS,EAAE,MAAM,CAAC;KACrB,CAAC;CACL,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC3B,IAAI,EAAE,YAAY,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAElB,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,SAAS,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,uBAAuB,CAAC;CACrC,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,cAAc,CAAC,gBAAgB,CAqE7D,CAAC;AAEF,eAAO,MAAM,uBAAuB,WACxB,MAAM,KACf,2BA6EF,CAAC"}
|
|
@@ -42,6 +42,18 @@ exports.databricksSchema = {
|
|
|
42
42
|
type: 'string',
|
|
43
43
|
nullable: true,
|
|
44
44
|
},
|
|
45
|
+
access_token: {
|
|
46
|
+
type: 'string',
|
|
47
|
+
nullable: true,
|
|
48
|
+
},
|
|
49
|
+
refresh_token: {
|
|
50
|
+
type: 'string',
|
|
51
|
+
nullable: true,
|
|
52
|
+
},
|
|
53
|
+
oauth_client_id: {
|
|
54
|
+
type: 'string',
|
|
55
|
+
nullable: true,
|
|
56
|
+
},
|
|
45
57
|
threads: {
|
|
46
58
|
type: 'number',
|
|
47
59
|
nullable: true,
|
|
@@ -70,19 +82,33 @@ const convertDatabricksSchema = (target) => {
|
|
|
70
82
|
throw new common_1.ParseError(`Couldn't read profiles.yml file for ${target.type}:\n${errs}`);
|
|
71
83
|
}
|
|
72
84
|
const authType = target.auth_type || 'token';
|
|
73
|
-
// OAuth
|
|
85
|
+
// OAuth authentication
|
|
74
86
|
if (authType === 'oauth') {
|
|
75
|
-
|
|
76
|
-
|
|
87
|
+
// Determine authentication type: check env var first, then auto-detect
|
|
88
|
+
let authenticationType;
|
|
89
|
+
const databricksOAuthEnv = process.env.DATABRICKS_OAUTH?.toLowerCase();
|
|
90
|
+
if (databricksOAuthEnv === 'u2m') {
|
|
91
|
+
// Force U2M (user-to-machine) - browser-based OAuth
|
|
92
|
+
authenticationType = common_1.DatabricksAuthenticationType.OAUTH_U2M;
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
// Auto-detect based on presence of client credentials
|
|
96
|
+
// If both client_id and client_secret are present, assume M2M
|
|
97
|
+
// Otherwise, assume U2M (which uses PKCE and doesn't require secret)
|
|
98
|
+
authenticationType =
|
|
99
|
+
target.client_secret && target.client_id
|
|
100
|
+
? common_1.DatabricksAuthenticationType.OAUTH_M2M
|
|
101
|
+
: common_1.DatabricksAuthenticationType.OAUTH_U2M;
|
|
77
102
|
}
|
|
103
|
+
const clientId = target.client_id || 'dbt-databricks'; // Use the same default dbt client for databricks
|
|
78
104
|
return {
|
|
79
105
|
type: common_1.WarehouseTypes.DATABRICKS,
|
|
80
|
-
authenticationType
|
|
106
|
+
authenticationType,
|
|
81
107
|
catalog: target.catalog,
|
|
82
108
|
database: target.schema,
|
|
83
109
|
serverHostName: target.host,
|
|
84
110
|
httpPath: target.http_path,
|
|
85
|
-
oauthClientId:
|
|
111
|
+
oauthClientId: clientId,
|
|
86
112
|
oauthClientSecret: target.client_secret,
|
|
87
113
|
compute: Object.entries(target.compute || {}).map(([name, compute]) => ({
|
|
88
114
|
name,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getWarehouseClient.d.ts","sourceRoot":"","sources":["../../../src/handlers/dbt/getWarehouseClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,0BAA0B,EAQ1B,oBAAoB,EAEvB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAEH,8BAA8B,EACjC,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"getWarehouseClient.d.ts","sourceRoot":"","sources":["../../../src/handlers/dbt/getWarehouseClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,0BAA0B,EAQ1B,oBAAoB,EAEvB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAEH,8BAA8B,EACjC,MAAM,uBAAuB,CAAC;AAwC/B,KAAK,oBAAoB,GAAG;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,eAAO,MAAM,cAAc,0DAKxB,oBAAoB,kCAKjB,CAAC;AA8HP,KAAK,yBAAyB,GAAG;IAC7B,aAAa,EAAE,OAAO,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,KAAK,wBAAwB,GAAG;IAC5B,eAAe,EAAE,UAAU,CAAC,OAAO,8BAA8B,CAAC,CAAC;IACnE,WAAW,EAAE,0BAA0B,CAAC;CAC3C,CAAC;AAEF,wBAA8B,kBAAkB,CAC5C,OAAO,EAAE,yBAAyB,GACnC,OAAO,CAAC,wBAAwB,CAAC,CA+KnC"}
|
|
@@ -10,7 +10,9 @@ const execa_1 = tslib_1.__importDefault(require("execa"));
|
|
|
10
10
|
const path_1 = tslib_1.__importDefault(require("path"));
|
|
11
11
|
const config_1 = require("../../config");
|
|
12
12
|
const profile_1 = require("../../dbt/profile");
|
|
13
|
+
const oauth_1 = require("../../dbt/targets/Databricks/oauth");
|
|
13
14
|
const globalState_1 = tslib_1.__importDefault(require("../../globalState"));
|
|
15
|
+
const styles = tslib_1.__importStar(require("../../styles"));
|
|
14
16
|
const apiClient_1 = require("./apiClient");
|
|
15
17
|
/**
|
|
16
18
|
* Cache warehouse clients to avoid repeated authentication prompts
|
|
@@ -217,7 +219,7 @@ async function getWarehouseClient(options) {
|
|
|
217
219
|
});
|
|
218
220
|
globalState_1.default.debug(`> Using target ${target.type}`);
|
|
219
221
|
credentials = await (0, profile_1.warehouseCredentialsFromDbtTarget)(target);
|
|
220
|
-
// Exchange Databricks OAuth credentials for access token if needed
|
|
222
|
+
// Exchange Databricks OAuth M2M credentials for access token if needed
|
|
221
223
|
if (credentials.type === common_1.WarehouseTypes.DATABRICKS &&
|
|
222
224
|
credentials.authenticationType ===
|
|
223
225
|
common_1.DatabricksAuthenticationType.OAUTH_M2M &&
|
|
@@ -225,8 +227,30 @@ async function getWarehouseClient(options) {
|
|
|
225
227
|
credentials.oauthClientSecret &&
|
|
226
228
|
!credentials.token) {
|
|
227
229
|
globalState_1.default.debug(`> Exchanging Databricks OAuth credentials for access token`);
|
|
228
|
-
|
|
229
|
-
|
|
230
|
+
try {
|
|
231
|
+
const { accessToken } = await (0, warehouses_1.exchangeDatabricksOAuthCredentials)(credentials.serverHostName, credentials.oauthClientId, credentials.oauthClientSecret);
|
|
232
|
+
credentials.token = accessToken;
|
|
233
|
+
}
|
|
234
|
+
catch (e) {
|
|
235
|
+
globalState_1.default.debug(`> Failed to exchange Databricks OAuth credentials for access token: ${(0, common_1.getErrorMessage)(e)}`);
|
|
236
|
+
console.warn(styles.error(`\nFailed to authenticate with Databricks using M2M OAuth (client_id and client_secret). ` +
|
|
237
|
+
`Perhaps you meant to use U2M OAuth instead? Set DATABRICKS_OAUTH=u2m environment variable to force U2M authentication.`));
|
|
238
|
+
process.exit(1);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// Handle Databricks OAuth U2M authentication
|
|
242
|
+
if (credentials.type === common_1.WarehouseTypes.DATABRICKS &&
|
|
243
|
+
credentials.authenticationType ===
|
|
244
|
+
common_1.DatabricksAuthenticationType.OAUTH_U2M &&
|
|
245
|
+
!credentials.token) {
|
|
246
|
+
// No tokens - perform OAuth flow (tokens kept in memory only)
|
|
247
|
+
console.error(`\nDatabricks OAuth authentication required for ${credentials.serverHostName}`);
|
|
248
|
+
const clientId = credentials.oauthClientId || 'dbt-databricks'; // Use the same default dbt client for databricks
|
|
249
|
+
const tokens = await (0, oauth_1.performDatabricksOAuthFlow)(credentials.serverHostName, clientId, credentials.oauthClientSecret);
|
|
250
|
+
// Store tokens in memory only
|
|
251
|
+
credentials.token = tokens.accessToken;
|
|
252
|
+
credentials.refreshToken = tokens.refreshToken;
|
|
253
|
+
console.error(`\n✓ Successfully authenticated with Databricks\n`);
|
|
230
254
|
}
|
|
231
255
|
// Check if we should use cached client (e.g., for auth methods requiring user interaction)
|
|
232
256
|
const cacheKey = getWarehouseClientCacheKey(credentials);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../../../src/handlers/login/oauth.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,WAAW,QAAe,MAAM,KAAG,OAAO,CAAC,IAAI,CAc3D,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Helper function to open browser
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.openBrowser = void 0;
|
|
5
|
+
const tslib_1 = require("tslib");
|
|
6
|
+
const child_process_1 = require("child_process");
|
|
7
|
+
const util_1 = require("util");
|
|
8
|
+
const globalState_1 = tslib_1.__importDefault(require("../../globalState"));
|
|
9
|
+
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
10
|
+
const openBrowser = async (url) => {
|
|
11
|
+
try {
|
|
12
|
+
const { platform } = process;
|
|
13
|
+
if (platform === 'darwin') {
|
|
14
|
+
await execAsync(`open "${url}"`);
|
|
15
|
+
}
|
|
16
|
+
else if (platform === 'win32') {
|
|
17
|
+
await execAsync(`start "${url}"`);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
await execAsync(`xdg-open "${url}"`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
globalState_1.default.debug(`> Could not open browser automatically: ${error}`);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
exports.openBrowser = openBrowser;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauthLogin.d.ts","sourceRoot":"","sources":["../../src/handlers/oauthLogin.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"oauthLogin.d.ts","sourceRoot":"","sources":["../../src/handlers/oauthLogin.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,cAAc,QAClB,MAAM,KACZ,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CA8OvE,CAAC"}
|
|
@@ -3,34 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.loginWithOauth = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const common_1 = require("@lightdash/common");
|
|
6
|
-
const child_process_1 = require("child_process");
|
|
7
6
|
const http = tslib_1.__importStar(require("http"));
|
|
8
7
|
const node_fetch_1 = tslib_1.__importDefault(require("node-fetch"));
|
|
9
8
|
const openid_client_1 = require("openid-client");
|
|
10
9
|
const url_1 = require("url");
|
|
11
|
-
const util_1 = require("util");
|
|
12
10
|
const globalState_1 = tslib_1.__importDefault(require("../globalState"));
|
|
13
11
|
const styles = tslib_1.__importStar(require("../styles"));
|
|
12
|
+
const oauth_1 = require("./login/oauth");
|
|
14
13
|
const pat_1 = require("./login/pat");
|
|
15
|
-
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
16
|
-
// Helper function to open browser
|
|
17
|
-
const openBrowser = async (url) => {
|
|
18
|
-
try {
|
|
19
|
-
const { platform } = process;
|
|
20
|
-
if (platform === 'darwin') {
|
|
21
|
-
await execAsync(`open "${url}"`);
|
|
22
|
-
}
|
|
23
|
-
else if (platform === 'win32') {
|
|
24
|
-
await execAsync(`start "${url}"`);
|
|
25
|
-
}
|
|
26
|
-
else {
|
|
27
|
-
await execAsync(`xdg-open "${url}"`);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
catch (error) {
|
|
31
|
-
globalState_1.default.debug(`> Could not open browser automatically: ${error}`);
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
14
|
const loginWithOauth = async (url) => {
|
|
35
15
|
// Create a promise that will be resolved when we get the authorization code
|
|
36
16
|
let resolveAuth;
|
|
@@ -141,7 +121,7 @@ const loginWithOauth = async (url) => {
|
|
|
141
121
|
console.error(`If the browser doesn't open automatically, please visit:`);
|
|
142
122
|
console.error(`${styles.secondary(authUrl)}\n`);
|
|
143
123
|
// Try to open the browser
|
|
144
|
-
await openBrowser(authUrl);
|
|
124
|
+
await (0, oauth_1.openBrowser)(authUrl);
|
|
145
125
|
// Wait for the authorization code
|
|
146
126
|
const { code } = await authPromise;
|
|
147
127
|
globalState_1.default.debug(`> Got authorization code ${code.substring(0, 10)}...`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightdash/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2180.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"bin": {
|
|
6
6
|
"lightdash": "dist/index.js"
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"parse-node-version": "^2.0.0",
|
|
34
34
|
"unique-names-generator": "^4.7.1",
|
|
35
35
|
"uuid": "^11.0.3",
|
|
36
|
-
"@lightdash/common": "0.
|
|
37
|
-
"@lightdash/warehouses": "0.
|
|
36
|
+
"@lightdash/common": "0.2180.0",
|
|
37
|
+
"@lightdash/warehouses": "0.2180.0"
|
|
38
38
|
},
|
|
39
39
|
"description": "Lightdash CLI tool",
|
|
40
40
|
"devDependencies": {
|