feat(Snowflake Node): Add support for Key-Pair authentication (#14833)

This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™
2025-05-13 10:43:54 +02:00
committed by GitHub
parent c02696241b
commit 4302c5f474
4 changed files with 137 additions and 8 deletions

View File

@@ -30,11 +30,33 @@ export class Snowflake implements ICredentialType {
description: description:
'The default virtual warehouse to use for the session after connecting. Used for performing queries, loading data, etc.', 'The default virtual warehouse to use for the session after connecting. Used for performing queries, loading data, etc.',
}, },
{
displayName: 'Authentication',
name: 'authentication',
type: 'options',
options: [
{
name: 'Password',
value: 'password',
},
{
name: 'Key-Pair',
value: 'keyPair',
},
],
default: 'password',
description: 'The way to authenticate with Snowflake',
},
{ {
displayName: 'Username', displayName: 'Username',
name: 'username', name: 'username',
type: 'string', type: 'string',
default: '', default: '',
displayOptions: {
show: {
authentication: ['password'],
},
},
}, },
{ {
displayName: 'Password', displayName: 'Password',
@@ -44,6 +66,28 @@ export class Snowflake implements ICredentialType {
password: true, password: true,
}, },
default: '', default: '',
displayOptions: {
show: {
authentication: ['password'],
},
},
},
{
displayName: 'Private Key',
name: 'privateKey',
type: 'string',
typeOptions: {
password: true,
rows: 4,
},
default: '',
required: true,
displayOptions: {
show: {
authentication: ['keyPair'],
},
},
description: 'Private PEM key for Key-pair authentication with Snowflake',
}, },
{ {
displayName: 'Schema', displayName: 'Schema',

View File

@@ -1,5 +1,43 @@
import pick from 'lodash/pick';
import type snowflake from 'snowflake-sdk'; import type snowflake from 'snowflake-sdk';
const commonConnectionFields = [
'account',
'database',
'schema',
'warehouse',
'role',
'clientSessionKeepAlive',
] as const;
export type SnowflakeCredential = Pick<
snowflake.ConnectionOptions,
(typeof commonConnectionFields)[number]
> &
(
| {
authentication: 'password';
username?: string;
password?: string;
}
| {
authentication: 'keyPair';
privateKey: string;
}
);
export const getConnectionOptions = (credential: SnowflakeCredential) => {
const connectionOptions: snowflake.ConnectionOptions = pick(credential, commonConnectionFields);
if (credential.authentication === 'keyPair') {
connectionOptions.authenticator = 'SNOWFLAKE_JWT';
connectionOptions.privateKey = credential.privateKey;
} else {
connectionOptions.username = credential.username;
connectionOptions.password = credential.password;
}
return connectionOptions;
};
export async function connect(conn: snowflake.Connection) { export async function connect(conn: snowflake.Connection) {
return await new Promise<void>((resolve, reject) => { return await new Promise<void>((resolve, reject) => {
conn.connect((error) => (error ? reject(error) : resolve())); conn.connect((error) => (error ? reject(error) : resolve()));

View File

@@ -10,7 +10,13 @@ import snowflake from 'snowflake-sdk';
import { getResolvables } from '@utils/utilities'; import { getResolvables } from '@utils/utilities';
import { connect, destroy, execute } from './GenericFunctions'; import {
connect,
destroy,
execute,
getConnectionOptions,
type SnowflakeCredential,
} from './GenericFunctions';
export class Snowflake implements INodeType { export class Snowflake implements INodeType {
description: INodeTypeDescription = { description: INodeTypeDescription = {
@@ -164,16 +170,14 @@ export class Snowflake implements INodeType {
}; };
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> { async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const credentials = (await this.getCredentials( const credentials = await this.getCredentials<SnowflakeCredential>('snowflake');
'snowflake',
)) as unknown as snowflake.ConnectionOptions;
const returnData: INodeExecutionData[] = [];
let responseData;
const connection = snowflake.createConnection(credentials); const connectionOptions = getConnectionOptions(credentials);
const connection = snowflake.createConnection(connectionOptions);
await connect(connection); await connect(connection);
const returnData: INodeExecutionData[] = [];
const items = this.getInputData(); const items = this.getInputData();
const operation = this.getNodeParameter('operation', 0); const operation = this.getNodeParameter('operation', 0);
@@ -189,7 +193,7 @@ export class Snowflake implements INodeType {
query = query.replace(resolvable, this.evaluateExpression(resolvable, i) as string); query = query.replace(resolvable, this.evaluateExpression(resolvable, i) as string);
} }
responseData = await execute(connection, query, []); const responseData = await execute(connection, query, []);
const executionData = this.helpers.constructExecutionMetaData( const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject[]), this.helpers.returnJsonArray(responseData as IDataObject[]),
{ itemData: { item: i } }, { itemData: { item: i } },

View File

@@ -0,0 +1,43 @@
import { getConnectionOptions } from '../GenericFunctions';
describe('getConnectionOptions', () => {
const commonOptions = {
account: 'test-account',
database: 'test-database',
schema: 'test-schema',
warehouse: 'test-warehouse',
role: 'test-role',
clientSessionKeepAlive: true,
};
describe('should return connection options', () => {
it('with username and password for password authentication', () => {
const result = getConnectionOptions({
...commonOptions,
authentication: 'password',
username: 'test-username',
password: 'test-password',
});
expect(result).toEqual({
...commonOptions,
username: 'test-username',
password: 'test-password',
});
});
it('with private key for keyPair authentication', () => {
const result = getConnectionOptions({
...commonOptions,
authentication: 'keyPair',
privateKey: 'test-private-key',
});
expect(result).toEqual({
...commonOptions,
authenticator: 'SNOWFLAKE_JWT',
privateKey: 'test-private-key',
});
});
});
});