feature: add database and non http credentials test

Add credential testing to Postgres, MySQL, MicrosoftSQL, Redis, FTP, SFTP, IMAP, RabbitMQ and MQTT

Co-authored-by: Omar Ajoue <krynble@gmail.com>
This commit is contained in:
agobrech 2022-09-01 14:29:15 +02:00 committed by GitHub
parent b5511e5ac7
commit d82e87979d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 438 additions and 3 deletions

View file

@ -3,8 +3,12 @@ import {
createDeferredPromise, createDeferredPromise,
IBinaryData, IBinaryData,
IBinaryKeyData, IBinaryKeyData,
ICredentialDataDecryptedObject,
ICredentialsDecrypted,
ICredentialTestFunctions,
IDataObject, IDataObject,
IDeferredPromise, IDeferredPromise,
INodeCredentialTestResult,
INodeExecutionData, INodeExecutionData,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
@ -43,6 +47,7 @@ export class EmailReadImap implements INodeType {
{ {
name: 'imap', name: 'imap',
required: true, required: true,
testedBy: 'imapConnectionTest',
}, },
], ],
properties: [ properties: [
@ -171,6 +176,51 @@ export class EmailReadImap implements INodeType {
], ],
}; };
methods = {
credentialTest: {
async imapConnectionTest(
this: ICredentialTestFunctions,
credential: ICredentialsDecrypted,
): Promise<INodeCredentialTestResult> {
const credentials = credential.data as ICredentialDataDecryptedObject;
try {
const config: ImapSimpleOptions = {
imap: {
user: credentials.user as string,
password: credentials.password as string,
host: credentials.host as string,
port: credentials.port as number,
tls: credentials.secure as boolean,
authTimeout: 20000,
},
};
const tlsOptions: IDataObject = {};
if (credentials.secure) {
tlsOptions.servername = credentials.host as string;
}
if (!_.isEmpty(tlsOptions)) {
config.imap.tlsOptions = tlsOptions;
}
const conn = imapConnect(config).then(async (conn) => {
return conn;
});
(await conn).getBoxes((err, boxes) => {});
} catch (error) {
console.log(error);
return {
status: 'Error',
message: error.message,
};
}
return {
status: 'OK',
message: 'Connection successful!',
};
},
},
};
async trigger(this: ITriggerFunctions): Promise<ITriggerResponse> { async trigger(this: ITriggerFunctions): Promise<ITriggerResponse> {
const credentials = await this.getCredentials('imap'); const credentials = await this.getCredentials('imap');

View file

@ -1,7 +1,10 @@
import { IExecuteFunctions } from 'n8n-core'; import { IExecuteFunctions } from 'n8n-core';
import { import {
ICredentialDataDecryptedObject, ICredentialDataDecryptedObject,
ICredentialsDecrypted,
ICredentialTestFunctions,
IDataObject, IDataObject,
INodeCredentialTestResult,
INodeExecutionData, INodeExecutionData,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
@ -56,6 +59,7 @@ export class Ftp implements INodeType {
protocol: ['ftp'], protocol: ['ftp'],
}, },
}, },
testedBy: 'ftpConnectionTest',
}, },
{ {
// nodelinter-ignore-next-line // nodelinter-ignore-next-line
@ -66,6 +70,7 @@ export class Ftp implements INodeType {
protocol: ['sftp'], protocol: ['sftp'],
}, },
}, },
testedBy: 'sftpConnectionTest',
}, },
], ],
properties: [ properties: [
@ -348,6 +353,63 @@ export class Ftp implements INodeType {
], ],
}; };
methods = {
credentialTest: {
async ftpConnectionTest(
this: ICredentialTestFunctions,
credential: ICredentialsDecrypted,
): Promise<INodeCredentialTestResult> {
const credentials = credential.data as ICredentialDataDecryptedObject;
try {
let ftp: ftpClient;
ftp = new ftpClient();
await ftp.connect({
host: credentials.host as string,
port: credentials.port as number,
user: credentials.username as string,
password: credentials.password as string,
});
} catch (error) {
return {
status: 'Error',
message: error.message,
};
}
return {
status: 'OK',
message: 'Connection successful!',
};
},
async sftpConnectionTest(
this: ICredentialTestFunctions,
credential: ICredentialsDecrypted,
): Promise<INodeCredentialTestResult> {
const credentials = credential.data as ICredentialDataDecryptedObject;
try {
let sftp: sftpClient;
sftp = new sftpClient();
await sftp.connect({
host: credentials.host as string,
port: credentials.port as number,
username: credentials.username as string,
password: credentials.password as string,
privateKey: credentials.privateKey as string | undefined,
passphrase: credentials.passphrase as string | undefined,
});
} catch (error) {
return {
status: 'Error',
message: error.message,
};
}
return {
status: 'OK',
message: 'Connection successful!',
};
},
},
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> { async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData(); const items = this.getInputData();
// const returnData: IDataObject[] = []; // const returnData: IDataObject[] = [];

View file

@ -1,6 +1,15 @@
import { IExecuteFunctions } from 'n8n-core'; import { IExecuteFunctions } from 'n8n-core';
import { IDataObject, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow'; import {
ICredentialDataDecryptedObject,
ICredentialsDecrypted,
ICredentialTestFunctions,
IDataObject,
INodeCredentialTestResult,
INodeExecutionData,
INodeType,
INodeTypeDescription,
} from 'n8n-workflow';
import mqtt from 'mqtt'; import mqtt from 'mqtt';
@ -23,6 +32,7 @@ export class Mqtt implements INodeType {
{ {
name: 'mqtt', name: 'mqtt',
required: true, required: true,
testedBy: 'mqttConnectionTest',
}, },
], ],
properties: [ properties: [
@ -96,6 +106,83 @@ export class Mqtt implements INodeType {
], ],
}; };
methods = {
credentialTest: {
async mqttConnectionTest(
this: ICredentialTestFunctions,
credential: ICredentialsDecrypted,
): Promise<INodeCredentialTestResult> {
const credentials = credential.data as ICredentialDataDecryptedObject;
try {
const protocol = (credentials.protocol as string) || 'mqtt';
const host = credentials.host as string;
const brokerUrl = `${protocol}://${host}`;
const port = (credentials.port as number) || 1883;
const clientId =
(credentials.clientId as string) || `mqttjs_${Math.random().toString(16).substr(2, 8)}`;
const clean = credentials.clean as boolean;
const ssl = credentials.ssl as boolean;
const ca = credentials.ca as string;
const cert = credentials.cert as string;
const key = credentials.key as string;
const rejectUnauthorized = credentials.rejectUnauthorized as boolean;
let client: mqtt.MqttClient;
if (ssl === false) {
const clientOptions: IClientOptions = {
port,
clean,
clientId,
};
if (credentials.username && credentials.password) {
clientOptions.username = credentials.username as string;
clientOptions.password = credentials.password as string;
}
client = mqtt.connect(brokerUrl, clientOptions);
} else {
const clientOptions: IClientOptions = {
port,
clean,
clientId,
ca,
cert,
key,
rejectUnauthorized,
};
if (credentials.username && credentials.password) {
clientOptions.username = credentials.username as string;
clientOptions.password = credentials.password as string;
}
client = mqtt.connect(brokerUrl, clientOptions);
}
// tslint:disable-next-line: no-any
await new Promise((resolve, reject): any => {
client.on('connect', (test) => {
resolve(test);
client.end();
});
client.on('error', (error) => {
client.end();
reject(error);
});
});
} catch (error) {
return {
status: 'Error',
message: error.message,
};
}
return {
status: 'OK',
message: 'Connection successful!',
};
},
},
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> { async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData(); const items = this.getInputData();
const length = items.length; const length = items.length;

View file

@ -1,7 +1,11 @@
import { IExecuteFunctions } from 'n8n-core'; import { IExecuteFunctions } from 'n8n-core';
import { import {
ICredentialDataDecryptedObject,
ICredentialsDecrypted,
ICredentialTestFunctions,
IDataObject, IDataObject,
INodeCredentialTestResult,
INodeExecutionData, INodeExecutionData,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
@ -42,6 +46,7 @@ export class MicrosoftSql implements INodeType {
{ {
name: 'microsoftSql', name: 'microsoftSql',
required: true, required: true,
testedBy: 'microsoftSqlConnectionTest',
}, },
], ],
properties: [ properties: [
@ -213,6 +218,44 @@ export class MicrosoftSql implements INodeType {
], ],
}; };
methods = {
credentialTest: {
async microsoftSqlConnectionTest(
this: ICredentialTestFunctions,
credential: ICredentialsDecrypted,
): Promise<INodeCredentialTestResult> {
const credentials = credential.data as ICredentialDataDecryptedObject;
try {
const config = {
server: credentials.server as string,
port: credentials.port as number,
database: credentials.database as string,
user: credentials.user as string,
password: credentials.password as string,
domain: credentials.domain ? (credentials.domain as string) : undefined,
connectionTimeout: credentials.connectTimeout as number,
requestTimeout: credentials.requestTimeout as number,
options: {
encrypt: credentials.tls as boolean,
enableArithAbort: false,
},
};
const pool = new mssql.ConnectionPool(config);
await pool.connect();
} catch (error) {
return {
status: 'Error',
message: error.message,
};
}
return {
status: 'OK',
message: 'Connection successful!',
};
},
},
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> { async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const credentials = await this.getCredentials('microsoftSql'); const credentials = await this.getCredentials('microsoftSql');

View file

@ -1,6 +1,9 @@
import { IExecuteFunctions } from 'n8n-core';
import { import {
ICredentialDataDecryptedObject,
ICredentialsDecrypted,
ICredentialTestFunctions,
IDataObject, IDataObject,
INodeCredentialTestResult,
INodeExecutionData, INodeExecutionData,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
@ -10,6 +13,7 @@ import {
import mysql2 from 'mysql2/promise'; import mysql2 from 'mysql2/promise';
import { copyInputItems } from './GenericFunctions'; import { copyInputItems } from './GenericFunctions';
import { IExecuteFunctions } from 'n8n-core';
export class MySql implements INodeType { export class MySql implements INodeType {
description: INodeTypeDescription = { description: INodeTypeDescription = {
@ -28,6 +32,7 @@ export class MySql implements INodeType {
{ {
name: 'mySql', name: 'mySql',
required: true, required: true,
testedBy: 'mysqlConnectionTest',
}, },
], ],
properties: [ properties: [
@ -204,6 +209,46 @@ export class MySql implements INodeType {
], ],
}; };
methods = {
credentialTest: {
async mysqlConnectionTest(
this: ICredentialTestFunctions,
credential: ICredentialsDecrypted,
): Promise<INodeCredentialTestResult> {
const credentials = credential.data as ICredentialDataDecryptedObject;
try {
const { ssl, caCertificate, clientCertificate, clientPrivateKey, ...baseCredentials } =
credentials;
if (ssl) {
baseCredentials.ssl = {};
if (caCertificate) {
baseCredentials.ssl.ca = caCertificate;
}
if (clientCertificate || clientPrivateKey) {
baseCredentials.ssl.cert = clientCertificate;
baseCredentials.ssl.key = clientPrivateKey;
}
}
const connection = await mysql2.createConnection(baseCredentials);
connection.end();
} catch (error) {
return {
status: 'Error',
message: error.message,
};
}
return {
status: 'OK',
message: 'Connection successful!',
};
},
},
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> { async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const credentials = await this.getCredentials('mySql'); const credentials = await this.getCredentials('mySql');

View file

@ -1,6 +1,9 @@
import { IExecuteFunctions } from 'n8n-core'; import { IExecuteFunctions } from 'n8n-core';
import { import {
ICredentialsDecrypted,
ICredentialTestFunctions,
IDataObject, IDataObject,
INodeCredentialTestResult,
INodeExecutionData, INodeExecutionData,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
@ -28,6 +31,7 @@ export class Postgres implements INodeType {
{ {
name: 'postgres', name: 'postgres',
required: true, required: true,
testedBy: 'postgresConnectionTest',
}, },
], ],
properties: [ properties: [
@ -273,6 +277,48 @@ export class Postgres implements INodeType {
}, },
], ],
}; };
methods = {
credentialTest: {
async postgresConnectionTest(
this: ICredentialTestFunctions,
credential: ICredentialsDecrypted,
): Promise<INodeCredentialTestResult> {
const credentials = credential.data as IDataObject;
try {
const pgp = pgPromise();
const config: IDataObject = {
host: credentials.host as string,
port: credentials.port as number,
database: credentials.database as string,
user: credentials.user as string,
password: credentials.password as string,
};
if (credentials.allowUnauthorizedCerts === true) {
config.ssl = {
rejectUnauthorized: false,
};
} else {
config.ssl = !['disable', undefined].includes(credentials.ssl as string | undefined);
config.sslmode = (credentials.ssl as string) || 'disable';
}
const db = pgp(config);
await db.connect();
await pgp.end();
} catch (error) {
return {
status: 'Error',
message: error.message,
};
}
return {
status: 'OK',
message: 'Connection successful!',
};
},
},
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> { async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const credentials = await this.getCredentials('postgres'); const credentials = await this.getCredentials('postgres');

View file

@ -1,8 +1,11 @@
/* eslint-disable n8n-nodes-base/node-filename-against-convention */ /* eslint-disable n8n-nodes-base/node-filename-against-convention */
import { IExecuteFunctions } from 'n8n-core'; import { IExecuteFunctions } from 'n8n-core';
import * as amqplib from 'amqplib';
import { import {
ICredentialsDecrypted,
ICredentialTestFunctions,
IDataObject, IDataObject,
INodeCredentialTestResult,
INodeExecutionData, INodeExecutionData,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
@ -31,6 +34,7 @@ export class RabbitMQ implements INodeType {
{ {
name: 'rabbitmq', name: 'rabbitmq',
required: true, required: true,
testedBy: 'rabbitmqConnectionTest',
}, },
], ],
properties: [ properties: [
@ -273,6 +277,53 @@ export class RabbitMQ implements INodeType {
], ],
}; };
methods = {
credentialTest: {
async rabbitmqConnectionTest(
this: ICredentialTestFunctions,
credential: ICredentialsDecrypted,
): Promise<INodeCredentialTestResult> {
const credentials = credential.data as IDataObject;
try {
const credentialKeys = ['hostname', 'port', 'username', 'password', 'vhost'];
const credentialData: IDataObject = {};
credentialKeys.forEach((key) => {
credentialData[key] = credentials[key] === '' ? undefined : credentials[key];
});
const optsData: IDataObject = {};
if (credentials.ssl === true) {
credentialData.protocol = 'amqps';
optsData.ca =
credentials.ca === '' ? undefined : [Buffer.from(credentials.ca as string)];
if (credentials.passwordless === true) {
optsData.cert =
credentials.cert === '' ? undefined : Buffer.from(credentials.cert as string);
optsData.key =
credentials.key === '' ? undefined : Buffer.from(credentials.key as string);
optsData.passphrase =
credentials.passphrase === '' ? undefined : credentials.passphrase;
optsData.credentials = amqplib.credentials.external();
}
}
const connection = await amqplib.connect(credentialData, optsData);
await connection.close();
} catch (error) {
return {
status: 'Error',
message: error.message,
};
}
return {
status: 'OK',
message: 'Connection successful!',
};
},
},
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> { async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
let channel, options: IDataObject; let channel, options: IDataObject;
try { try {

View file

@ -1,7 +1,11 @@
import { IExecuteFunctions } from 'n8n-core'; import { IExecuteFunctions } from 'n8n-core';
import { import {
GenericValue, GenericValue,
ICredentialDataDecryptedObject,
ICredentialsDecrypted,
ICredentialTestFunctions,
IDataObject, IDataObject,
INodeCredentialTestResult,
INodeExecutionData, INodeExecutionData,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
@ -30,6 +34,7 @@ export class Redis implements INodeType {
{ {
name: 'redis', name: 'redis',
required: true, required: true,
testedBy: 'redisConnectionTest',
}, },
], ],
properties: [ properties: [
@ -489,6 +494,52 @@ export class Redis implements INodeType {
], ],
}; };
methods = {
credentialTest: {
async redisConnectionTest(
this: ICredentialTestFunctions,
credential: ICredentialsDecrypted,
): Promise<INodeCredentialTestResult> {
const credentials = credential.data as ICredentialDataDecryptedObject;
const redisOptions: redis.ClientOpts = {
host: credentials.host as string,
port: credentials.port as number,
db: credentials.database as number,
};
if (credentials.password) {
redisOptions.password = credentials.password as string;
}
try {
const client = await redis.createClient(redisOptions);
// tslint:disable-next-line: no-any
const data = await new Promise((resolve, reject): any => {
client.on('connect', async () => {
client.ping('ping', (error, pong) => {
if (error) reject(error);
resolve(pong);
client.quit();
});
});
client.on('error', async (err) => {
client.quit();
reject(err);
});
});
} catch (error) {
return {
status: 'Error',
message: error.message,
};
}
return {
status: 'OK',
message: 'Connection successful!',
};
},
},
};
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> { execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
// Parses the given value in a number if it is one else returns a string // Parses the given value in a number if it is one else returns a string
function getParsedValue(value: string): string | number { function getParsedValue(value: string): string | number {