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:
'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',
name: 'username',
type: 'string',
default: '',
displayOptions: {
show: {
authentication: ['password'],
},
},
},
{
displayName: 'Password',
@@ -44,6 +66,28 @@ export class Snowflake implements ICredentialType {
password: true,
},
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',

View File

@@ -1,5 +1,43 @@
import pick from 'lodash/pick';
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) {
return await new Promise<void>((resolve, reject) => {
conn.connect((error) => (error ? reject(error) : resolve()));

View File

@@ -10,7 +10,13 @@ import snowflake from 'snowflake-sdk';
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 {
description: INodeTypeDescription = {
@@ -164,16 +170,14 @@ export class Snowflake implements INodeType {
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const credentials = (await this.getCredentials(
'snowflake',
)) as unknown as snowflake.ConnectionOptions;
const returnData: INodeExecutionData[] = [];
let responseData;
const credentials = await this.getCredentials<SnowflakeCredential>('snowflake');
const connection = snowflake.createConnection(credentials);
const connectionOptions = getConnectionOptions(credentials);
const connection = snowflake.createConnection(connectionOptions);
await connect(connection);
const returnData: INodeExecutionData[] = [];
const items = this.getInputData();
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);
}
responseData = await execute(connection, query, []);
const responseData = await execute(connection, query, []);
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject[]),
{ 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',
});
});
});
});