Skip to content
Merged
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
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
save-exact=true
3 changes: 3 additions & 0 deletions common/errorMessages.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,7 @@ module.exports = {
KEY_PAIR_USERNAME_ERROR: 'Incorrect username was specified.',
KEY_PAIR_INVALID_FILE_ERROR:
"The selected file is not a valid key. Please choose a valid key file, check it's passphrase and try again.",
EXTERNAL_BROWSER_ERROR: 'Error while authenticating via external browser.',
SSO_REQUEST_ERROR: 'Cannot obtain the SSO URL.',
SSO_URL_ERROR: 'The SSO URL is nt provided in the JSON response',
};
20 changes: 19 additions & 1 deletion esbuild.package.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ esbuild
outdir: RELEASE_FOLDER_PATH,
minify: true,
logLevel: 'info',
external: ['lodash'],
external: ['lodash', '@hackolade/fetch'],
Copy link
Contributor

Choose a reason for hiding this comment

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

@Vitalii4as like for lodash if you put fetch as external you need to copy it into the node_modules (see below)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, I forgot about that(

plugins: [
clean({
patterns: [DEFAULT_RELEASE_FOLDER_PATH],
Expand All @@ -36,6 +36,24 @@ esbuild
to: [path.join('node_modules', 'lodash')],
},
}),
copy({
assets: {
from: [path.join('node_modules', '@hackolade', 'fetch', 'dist', 'cjs', '**', '*')],
to: [path.join('node_modules', '@hackolade', 'fetch', 'dist', 'cjs')],
},
}),
copy({
assets: {
from: [path.join('node_modules', '@hackolade', 'fetch', 'package.json')],
to: [path.join('node_modules', '@hackolade', 'fetch', 'package.json')],
},
}),
copy({
assets: {
from: [path.join('node_modules', '@hackolade', 'fetch', 'LICENSE')],
to: [path.join('node_modules', '@hackolade', 'fetch')],
},
}),
copyFolderFiles({
fromPath: __dirname,
targetFolderPath: RELEASE_FOLDER_PATH,
Expand Down
17,600 changes: 9,718 additions & 7,882 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,11 @@
"description": "Hackolade plugin for Snowflake",
"disabled": false,
"dependencies": {
"@hackolade/fetch": "1.3.0",
"async": "3.2.6",
"axios": "1.12.2",
"bson": "4.6.1",
"lodash": "4.17.21",
"snowflake-sdk": "2.0.4",
"snowflake-sdk": "2.3.3",
"uuid": "7.0.3"
},
"lint-staged": {
Expand All @@ -76,6 +76,7 @@
"pre-push": "npx eslint ."
},
"scripts": {
"install": "npx patch-package",
"lint": "eslint . --max-warnings=0",
"package": "node esbuild.package.js"
},
Expand Down
95 changes: 95 additions & 0 deletions patches/snowflake-sdk+2.3.3.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
diff --git a/node_modules/snowflake-sdk/dist/lib/agent/crl_fetcher.js b/node_modules/snowflake-sdk/dist/lib/agent/crl_fetcher.js
index 1424f11..dd0ffd1 100644
--- a/node_modules/snowflake-sdk/dist/lib/agent/crl_fetcher.js
+++ b/node_modules/snowflake-sdk/dist/lib/agent/crl_fetcher.js
@@ -5,8 +5,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
Object.defineProperty(exports, "__esModule", { value: true });
exports.PENDING_FETCH_REQUESTS = void 0;
exports.getCrl = getCrl;
+const { hckFetch } = require('@hackolade/fetch');
const asn1_js_rfc5280_1 = __importDefault(require("asn1.js-rfc5280"));
-const axios_1 = __importDefault(require("axios"));
+const axios_1 = __importDefault(require("axios").create({
+ adapter: 'fetch',
+ env: {
+ fetch: hckFetch,
+ Request: null,
+ Response: null,
+ }
+}));
const logger_1 = __importDefault(require("../logger"));
const global_config_typed_1 = __importDefault(require("../global_config_typed"));
const crl_cache_1 = require("./crl_cache");
diff --git a/node_modules/snowflake-sdk/dist/lib/http/base.js b/node_modules/snowflake-sdk/dist/lib/http/base.js
index 7320edb..cb750e0 100644
--- a/node_modules/snowflake-sdk/dist/lib/http/base.js
+++ b/node_modules/snowflake-sdk/dist/lib/http/base.js
@@ -3,7 +3,15 @@ const zlib = require('zlib');
const Util = require('../util');
const Logger = require('../logger');
const ExecutionTimer = require('../logger/execution_timer');
-const axios = require('axios');
+const { hckFetch } = require('@hackolade/fetch');
+const axios = require('axios').create({
+ adapter: 'fetch',
+ env: {
+ fetch: hckFetch,
+ Request: null,
+ Response: null,
+ }
+});
const URL = require('node:url').URL;
Copy link
Contributor

Choose a reason for hiding this comment

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

@Vitalii4as i know that under certain condition the node protocol doesn't play well with our webpack. So just to make sure did you build it for browser and electron?

const requestUtil = require('./request_util');
const DEFAULT_REQUEST_TIMEOUT = 360000;
diff --git a/node_modules/snowflake-sdk/dist/lib/http/node_untyped.js b/node_modules/snowflake-sdk/dist/lib/http/node_untyped.js
index a361cbb..3e68446 100644
--- a/node_modules/snowflake-sdk/dist/lib/http/node_untyped.js
+++ b/node_modules/snowflake-sdk/dist/lib/http/node_untyped.js
@@ -90,15 +90,7 @@ function isBypassProxy(proxy, destination, agentId) {
* @inheritDoc
*/
NodeHttpClient.prototype.getAgent = function (parsedUrl, proxy, mock) {
- Logger.getInstance().trace('Agent[url: %s] - getting an agent instance.', RequestUtil.describeURL(parsedUrl.href));
- if (!proxy && GlobalConfig.isEnvProxyActive()) {
- const isHttps = parsedUrl.protocol === 'https:';
- proxy = ProxyUtil.getProxyFromEnv(isHttps);
- if (proxy) {
- Logger.getInstance().debug('Agent[url: %s] - proxy info loaded from the environment variable. Proxy host: %s', RequestUtil.describeURL(parsedUrl.href), proxy.host);
- }
- }
- return getProxyAgent(proxy, parsedUrl, parsedUrl.href, mock, this._connectionConfig);
+ return null;
};
function getProxyAgent(proxyOptions, parsedUrl, destination, mock, connectionConfig) {
Logger.getInstance().trace('Agent[url: %s] - getting a proxy agent instance.', RequestUtil.describeURL(parsedUrl.href));
diff --git a/node_modules/snowflake-sdk/dist/lib/minicore/minicore.js b/node_modules/snowflake-sdk/dist/lib/minicore/minicore.js
index 8c39f8e..73bae12 100644
--- a/node_modules/snowflake-sdk/dist/lib/minicore/minicore.js
+++ b/node_modules/snowflake-sdk/dist/lib/minicore/minicore.js
@@ -14,23 +14,9 @@ exports.minicoreStatus = {
error: null,
};
let logDebugMinicoreError = null;
-if (process.env.SNOWFLAKE_DISABLE_MINICORE) {
- exports.minicoreStatus.error = 'Minicore is disabled with SNOWFLAKE_DISABLE_MINICORE env variable';
-}
-else {
- try {
- exports.minicoreStatus.binaryName = getBinaryName();
- const minicoreModule = require(`./binaries/${exports.minicoreStatus.binaryName}`);
- exports.minicoreStatus.version = minicoreModule.sfCoreFullVersion();
- }
- catch (error) {
- // NOTE:
- // minicoreStatus is pushed to telemetry, so we don't want the original error there as it might
- // contain sensitive information
- logDebugMinicoreError = error;
- exports.minicoreStatus.error = 'Failed to load binary';
- }
-}
+
+exports.minicoreStatus.error = 'Minicore is disabled with SNOWFLAKE_DISABLE_MINICORE env variable';
+
// NOTE:
// Custom loader instead of napi-rs autogenerated binding file because:
// - napi-rs tries to require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH) which might be a security risk
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const _ = require('lodash');
const axios = require('axios');
const { hckFetch } = require('@hackolade/fetch');
const uuid = require('uuid');

const {
Expand All @@ -13,6 +13,7 @@ const {

const { getAccountName, getRole, removeQuotes } = require('../common');
const { connectWithTimeout, execute } = require('./connection');
const errorMessages = require('../../../common/errorMessages.js');

const authByExternalBrowser = async ({
token,
Expand All @@ -34,9 +35,9 @@ const authByExternalBrowser = async ({
role = role || DEFAULT_ROLE;
authUrl += `&roleName=${encodeURIComponent(getRole(role))}`;

const authData = await axios.post(
authUrl,
{
const authData = await hckFetch(authUrl, {
method: 'POST',
body: JSON.stringify({
data: {
CLIENT_APP_ID: DEFAULT_CLIENT_APP_ID,
CLIENT_APP_VERSION: DEFAULT_CLIENT_APP_VERSION,
Expand All @@ -49,15 +50,21 @@ const authByExternalBrowser = async ({
APPLICATION: HACKOLADE_APPLICATION,
},
},
}),
headers: {
Accept: 'application/json',
Authorization: 'Basic',
'Content-Type': 'application/json',
},
{
headers: {
Accept: 'application/json',
Authorization: 'Basic',
},
},
);
let tokensData = authData.data;
});

if (!authData.ok) {
return Promise.reject(
new Error(errorMessages.EXTERNAL_BROWSER_ERROR + ` Status ${authData.status} ${authData.statusText}`),
);
}

let tokensData = await authData.json();
if (_.isString(tokensData)) {
try {
tokensData = JSON.parse(tokensData);
Expand Down
89 changes: 59 additions & 30 deletions reverse_engineering/helpers/connections/oktaConnection.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
const _ = require('lodash');
const snowflake = require('snowflake-sdk');
const axios = require('axios');
const { hckFetch } = require('@hackolade/fetch');
const uuid = require('uuid');

const {
Expand Down Expand Up @@ -30,58 +29,82 @@ const authByOkta = async ({

logger.log('info', `Authenticator: ${authenticator}`, 'Connection');
const accountName = getAccountName(account);
const ssoUrlsData = await axios.post(
const ssoUrlsResponse = await hckFetch(
`${accessUrl}/session/authenticator-request?Application=${HACKOLADE_APPLICATION}`,
{
data: {
ACCOUNT_NAME: accountName,
LOGIN_NAME: username,
AUTHENTICATOR: getOktaAuthenticatorUrl(authenticator),
method: 'POST',
body: JSON.stringify({
data: {
ACCOUNT_NAME: accountName,
LOGIN_NAME: username,
AUTHENTICATOR: getOktaAuthenticatorUrl(authenticator),
},
}),
headers: {
'Content-Type': 'application/json',
},
},
);
const ssoUrlsData = await ssoUrlsResponse.json();

logger.log('info', `Starting Okta connection...`, 'Connection');
const tokenUrl = _.get(ssoUrlsData, 'data.data.tokenUrl', '');
const tokenUrl = _.get(ssoUrlsData, 'data.tokenUrl', '');
const authNUrl = tokenUrl.replace(/api\/v1\/.*/, 'api/v1/authn');
const ssoUrl = _.get(ssoUrlsData, 'data.data.ssoUrl', '');
const ssoUrl = _.get(ssoUrlsData, 'data.ssoUrl', '');
logger.log('info', `Token URL: ${tokenUrl}\nSSO URL: ${ssoUrl}`, 'Connection');

if (!tokenUrl || !ssoUrl) {
return Promise.reject({ message: errorMessages.OKTA_SSO_ERROR });
}

const authNData = await axios
.post(authNUrl, {
const authNData = await hckFetch(authNUrl, {
method: 'POST',
body: JSON.stringify({
username,
password,
options: {
multiOptionalFactorEnroll: false,
warnBeforePasswordExpired: false,
},
})
}),
headers: {
'Content-Type': 'application/json',
},
})
.then(res => (res.ok ? res.json() : {}))
.catch(err => ({}));
const status = _.get(authNData, 'data.status', 'SUCCESS');
const authToken = _.get(authNData, 'data.sessionToken', '');
const status = _.get(authNData, 'status', 'SUCCESS');
const authToken = _.get(authNData, 'sessionToken', '');
if (status.startsWith('MFA')) {
return Promise.reject({ message: errorMessages.OKTA_MFA_ERROR });
}

const identityProviderTokenData = await axios.post(tokenUrl, { username, password }).catch(err => {
return authToken ? {} : Promise.reject(oktaCredentialsError);
});
const identityProviderTokenData = await hckFetch(tokenUrl, {
method: 'POST',
body: JSON.stringify({ username, password }),
headers: {
'Content-Type': 'application/json',
},
})
.then(res => (res.ok ? res.json() : Promise.reject()))
.catch(err => (authToken ? {} : Promise.reject(oktaCredentialsError)));

logger.log('info', `Successfully connected to Okta`, 'Connection');
const identityProviderToken = _.get(identityProviderTokenData, 'data.cookieToken', '') || authToken;
const identityProviderToken = _.get(identityProviderTokenData, 'cookieToken', '') || authToken;
if (!identityProviderToken) {
return Promise.reject(oktaCredentialsError);
}

logger.log('info', `One-time IDP token has been provided`, 'Connection');

const samlUrl = `${ssoUrl}?onetimetoken=${encodeURIComponent(identityProviderToken)}&RelayState=${encodeURIComponent('/some/deep/link')}`;
const samlResponseData = await axios.get(samlUrl, { headers: { HTTP_HEADER_ACCEPT: '*/*' } });
const rawSamlResponse = _.get(samlResponseData, 'data', '');
const samlResponse = await hckFetch(samlUrl, {
method: 'GET',
headers: {
Accept: '*/*',
},
});
const rawSamlResponse = samlResponse.ok ? await samlResponse.text() : '';

if (!rawSamlResponse) {
logger.log('info', `Warning: RAW_SAML_RESPONSE is empty`, 'Connection');
Expand All @@ -96,19 +119,25 @@ const authByOkta = async ({
authUrl += `&roleName=${encodeURIComponent(getRole(role))}`;
authUrl += `&warehouse=${encodeURIComponent(warehouse)}`;

const authData = await axios.post(authUrl, {
data: {
CLIENT_APP_ID: DEFAULT_CLIENT_APP_ID,
CLIENT_APP_VERSION: DEFAULT_CLIENT_APP_VERSION,
RAW_SAML_RESPONSE: rawSamlResponse,
LOGIN_NAME: username,
ACCOUNT_NAME: accountName,
CLIENT_ENVIRONMENT: {
APPLICATION: HACKOLADE_APPLICATION,
const authResponse = await hckFetch(authUrl, {
method: 'POST',
body: JSON.stringify({
data: {
CLIENT_APP_ID: DEFAULT_CLIENT_APP_ID,
CLIENT_APP_VERSION: DEFAULT_CLIENT_APP_VERSION,
RAW_SAML_RESPONSE: rawSamlResponse,
LOGIN_NAME: username,
ACCOUNT_NAME: accountName,
CLIENT_ENVIRONMENT: {
APPLICATION: HACKOLADE_APPLICATION,
},
},
}),
headers: {
'Content-Type': 'application/json',
},
});
let tokensData = authData.data;
let tokensData = await authResponse.json();
if (_.isString(tokensData)) {
try {
tokensData = JSON.parse(tokensData);
Expand Down
1 change: 0 additions & 1 deletion reverse_engineering/helpers/snowflakeHelper.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
const _ = require('lodash');
const snowflake = require('snowflake-sdk');
const axios = require('axios');
const uuid = require('uuid');
const BSON = require('bson');
const errorMessages = require('../../common/errorMessages.js');
Expand Down
Loading