From 66d9fa78ab105012c2dcf2dfe35c5636e2341276 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Tue, 28 Jan 2020 17:39:43 -0500 Subject: [PATCH 1/3] :sparkles: Crypto helper --- packages/nodes-base/nodes/Crypto.node.ts | 358 +++++++++++++++++++++++ packages/nodes-base/package.json | 1 + 2 files changed, 359 insertions(+) create mode 100644 packages/nodes-base/nodes/Crypto.node.ts diff --git a/packages/nodes-base/nodes/Crypto.node.ts b/packages/nodes-base/nodes/Crypto.node.ts new file mode 100644 index 0000000000..dcf9963d89 --- /dev/null +++ b/packages/nodes-base/nodes/Crypto.node.ts @@ -0,0 +1,358 @@ +import { IExecuteFunctions } from 'n8n-core'; +import { + INodeExecutionData, + INodeType, + INodeTypeDescription, + IDataObject, + ILoadOptionsFunctions, + INodePropertyOptions, +} from 'n8n-workflow'; + +import { + createHash, + createHmac, + createSign, + getHashes, + } from 'crypto'; +import { c } from 'rhea/typings/types'; + +export class Crypto implements INodeType { + description: INodeTypeDescription = { + displayName: 'Crypto', + name: 'crypto', + icon: 'fa:key', + group: ['transform'], + version: 1, + description: 'Provide cryptographic utilities', + defaults: { + name: 'Crypto', + color: '#408000', + }, + inputs: ['main'], + outputs: ['main'], + properties: [ + { + displayName: 'Action', + name: 'action', + type: 'options', + options: [ + { + name: 'Hash', + description: 'Hash a text in a specified format.', + value: 'hash' + }, + { + name: 'Hmac', + description: 'Hmac a text in a specified format.', + value: 'hmac' + }, + { + name: 'Sign', + description: 'Sign a string using a private key.', + value: 'sign' + }, + ], + default: 'hash', + }, + { + displayName: 'Type', + name: 'type', + displayOptions: { + show: { + action:[ + 'hash' + ], + }, + }, + type: 'options', + options: [ + { + name: 'MD5', + value: 'MD5', + }, + { + name: 'SHA256', + value: 'SHA256', + }, + { + name: 'SHA512', + value: 'SHA512', + }, + ], + default: 'MD5', + description: 'The hash type to use', + required: true, + }, + { + displayName: 'Field Name', + name: 'fieldName', + displayOptions: { + show: { + action:[ + 'hash' + ], + }, + }, + type: 'string', + default: '', + required: true, + }, + { + displayName: 'Encoding', + name: 'encoding', + displayOptions: { + show: { + action:[ + 'hash' + ], + }, + }, + type: 'options', + options: [ + { + name: 'HEX', + value: 'hex', + }, + { + name: 'BASE64', + value: 'base64', + }, + ], + default: 'hex', + required: true, + }, + { + displayName: 'Type', + name: 'type', + displayOptions: { + show: { + action:[ + 'hmac' + ], + }, + }, + type: 'options', + options: [ + { + name: 'MD5', + value: 'MD5', + }, + { + name: 'SHA256', + value: 'SHA256', + }, + { + name: 'SHA512', + value: 'SHA512', + }, + ], + default: 'MD5', + description: 'The hash type to use', + required: true, + }, + { + displayName: 'Field Name', + name: 'fieldName', + displayOptions: { + show: { + action:[ + 'hmac' + ], + }, + }, + type: 'string', + default: '', + required: true, + }, + { + displayName: 'Secret', + name: 'secret', + displayOptions: { + show: { + action:[ + 'hmac' + ], + }, + }, + type: 'string', + default: '', + required: true, + }, + { + displayName: 'Encoding', + name: 'encoding', + displayOptions: { + show: { + action:[ + 'hmac', + ], + }, + }, + type: 'options', + options: [ + { + name: 'HEX', + value: 'hex', + }, + { + name: 'BASE64', + value: 'base64', + }, + ], + default: 'hex', + required: true, + }, + { + displayName: 'Field Name', + name: 'fieldName', + displayOptions: { + show: { + action:[ + 'sign' + ], + }, + }, + type: 'string', + default: '', + required: true, + }, + { + displayName: 'Algorithm', + name: 'algorithm', + displayOptions: { + show: { + action:[ + 'sign', + ], + }, + }, + type: 'options', + typeOptions: { + loadOptionsMethod: 'getHashes', + }, + default: '', + required: true, + }, + { + displayName: 'Encoding', + name: 'encoding', + displayOptions: { + show: { + action:[ + 'sign', + ], + }, + }, + type: 'options', + options: [ + { + name: 'HEX', + value: 'hex', + }, + { + name: 'BASE64', + value: 'base64', + }, + ], + default: 'hex', + required: true, + }, + { + displayName: 'Private Key', + name: 'privateKey', + displayOptions: { + show: { + action:[ + 'sign' + ], + }, + }, + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + description: 'Private key to use when signing the string.', + default: '', + required: true, + }, + ], + }; + + methods = { + loadOptions: { + // Get all the hashes to display them to user so that he can + // select them easily + async getHashes(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const hashes = getHashes(); + for (const hash of hashes) { + const hashName = hash; + const hashId = hash; + returnData.push({ + name: hashName, + value: hashId, + }); + } + return returnData; + }, + } + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + const length = items.length as unknown as number; + let responseData; + for (let i = 0; i < length; i++) { + const action = this.getNodeParameter('action', 0) as string; + if (action === 'hash') { + const type = this.getNodeParameter('type', i) as string; + const encoding = this.getNodeParameter('encoding', i) as string; + const fieldName = this.getNodeParameter('fieldName', i) as string; + const clone = { ...items[i].json }; + if (clone[fieldName] === undefined) { + throw new Error(`The field ${fieldName} does not exist on the input data`); + } + //@ts-ignore + clone[fieldName] = createHash(type).update(clone[fieldName] as string).digest(encoding); + responseData = clone; + } + if (action === 'hmac') { + const type = this.getNodeParameter('type', i) as string; + const secret = this.getNodeParameter('secret', i) as string; + const encoding = this.getNodeParameter('encoding', i) as string; + const fieldName = this.getNodeParameter('fieldName', i) as string; + const clone = { ...items[i].json }; + if (clone[fieldName] === undefined) { + throw new Error(`The field ${fieldName} does not exist on the input data`); + } + //@ts-ignore + clone[fieldName] = createHmac(type, secret).update(clone[fieldName] as string).digest(encoding); + responseData = clone; + } + if (action === 'sign') { + const algorithm = this.getNodeParameter('algorithm', i) as string; + const fieldName = this.getNodeParameter('fieldName', i) as string; + const encoding = this.getNodeParameter('encoding', i) as string; + const privateKey = this.getNodeParameter('privateKey', i) as IDataObject; + const clone = { ...items[i].json }; + if (clone[fieldName] === undefined) { + throw new Error(`The field ${fieldName} does not exist on the input data`); + } + const sign = createSign(algorithm) + sign.write(clone[fieldName] as string); + sign.end(); + //@ts-ignore + const signature = sign.sign(privateKey, encoding); + clone[fieldName] = signature; + responseData = clone; + } + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else { + returnData.push(responseData as IDataObject); + } + } + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 2c40a4d076..69771b6726 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -110,6 +110,7 @@ "dist/nodes/Coda/Coda.node.js", "dist/nodes/Copper/CopperTrigger.node.js", "dist/nodes/Cron.node.js", + "dist/nodes/Crypto.node.js", "dist/nodes/Discord/Discord.node.js", "dist/nodes/Disqus/Disqus.node.js", "dist/nodes/Dropbox/Dropbox.node.js", From 838770665b0472de76802c156e540c41277c0ffe Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Tue, 28 Jan 2020 17:40:30 -0500 Subject: [PATCH 2/3] :zap: small fix --- packages/nodes-base/nodes/Crypto.node.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/nodes-base/nodes/Crypto.node.ts b/packages/nodes-base/nodes/Crypto.node.ts index dcf9963d89..679f99580a 100644 --- a/packages/nodes-base/nodes/Crypto.node.ts +++ b/packages/nodes-base/nodes/Crypto.node.ts @@ -14,7 +14,6 @@ import { createSign, getHashes, } from 'crypto'; -import { c } from 'rhea/typings/types'; export class Crypto implements INodeType { description: INodeTypeDescription = { From 91688299b7c106c51ac366d1c9cdfcd2992e5f70 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 7 Feb 2020 22:33:53 -0800 Subject: [PATCH 3/3] :zap: Small modifications to Crypto-Node --- packages/nodes-base/nodes/Crypto.node.ts | 125 +++++++++++++++-------- 1 file changed, 82 insertions(+), 43 deletions(-) diff --git a/packages/nodes-base/nodes/Crypto.node.ts b/packages/nodes-base/nodes/Crypto.node.ts index 679f99580a..fe3943aab3 100644 --- a/packages/nodes-base/nodes/Crypto.node.ts +++ b/packages/nodes-base/nodes/Crypto.node.ts @@ -13,6 +13,7 @@ import { createHmac, createSign, getHashes, + HexBase64Latin1Encoding, } from 'crypto'; export class Crypto implements INodeType { @@ -22,6 +23,7 @@ export class Crypto implements INodeType { icon: 'fa:key', group: ['transform'], version: 1, + subtitle: '={{$parameter["action"]}}', description: 'Provide cryptographic utilities', defaults: { name: 'Crypto', @@ -83,8 +85,8 @@ export class Crypto implements INodeType { required: true, }, { - displayName: 'Field Name', - name: 'fieldName', + displayName: 'Value', + name: 'value', displayOptions: { show: { action:[ @@ -94,8 +96,24 @@ export class Crypto implements INodeType { }, type: 'string', default: '', + description: 'The value that should be hashed.', required: true, }, + { + displayName: 'Property Name', + name: 'dataPropertyName', + type: 'string', + default: 'data', + required: true, + displayOptions: { + show: { + action: [ + 'hash' + ], + }, + }, + description: 'Name of the property to which to write the hash.', + }, { displayName: 'Encoding', name: 'encoding', @@ -108,14 +126,14 @@ export class Crypto implements INodeType { }, type: 'options', options: [ - { - name: 'HEX', - value: 'hex', - }, { name: 'BASE64', value: 'base64', }, + { + name: 'HEX', + value: 'hex', + }, ], default: 'hex', required: true, @@ -150,8 +168,8 @@ export class Crypto implements INodeType { required: true, }, { - displayName: 'Field Name', - name: 'fieldName', + displayName: 'Value', + name: 'value', displayOptions: { show: { action:[ @@ -161,8 +179,24 @@ export class Crypto implements INodeType { }, type: 'string', default: '', + description: 'The value of which the hmac should be created.', required: true, }, + { + displayName: 'Property Name', + name: 'dataPropertyName', + type: 'string', + default: 'data', + required: true, + displayOptions: { + show: { + action: [ + 'hmac' + ], + }, + }, + description: 'Name of the property to which to write the hmac.', + }, { displayName: 'Secret', name: 'secret', @@ -189,21 +223,21 @@ export class Crypto implements INodeType { }, type: 'options', options: [ - { - name: 'HEX', - value: 'hex', - }, { name: 'BASE64', value: 'base64', }, + { + name: 'HEX', + value: 'hex', + }, ], default: 'hex', required: true, }, { - displayName: 'Field Name', - name: 'fieldName', + displayName: 'Value', + name: 'value', displayOptions: { show: { action:[ @@ -213,8 +247,24 @@ export class Crypto implements INodeType { }, type: 'string', default: '', + description: 'The value that should be signed.', required: true, }, + { + displayName: 'Property Name', + name: 'dataPropertyName', + type: 'string', + default: 'data', + required: true, + displayOptions: { + show: { + action: [ + 'sign' + ], + }, + }, + description: 'Name of the property to which to write the signed value.', + }, { displayName: 'Algorithm', name: 'algorithm', @@ -244,14 +294,14 @@ export class Crypto implements INodeType { }, type: 'options', options: [ - { - name: 'HEX', - value: 'hex', - }, { name: 'BASE64', value: 'base64', }, + { + name: 'HEX', + value: 'hex', + }, ], default: 'hex', required: true, @@ -302,48 +352,37 @@ export class Crypto implements INodeType { const returnData: IDataObject[] = []; const length = items.length as unknown as number; let responseData; + const action = this.getNodeParameter('action', 0) as string; + for (let i = 0; i < length; i++) { - const action = this.getNodeParameter('action', 0) as string; + const dataPropertyName = this.getNodeParameter('dataPropertyName', i) as string; + const value = this.getNodeParameter('value', i) as string; + if (action === 'hash') { const type = this.getNodeParameter('type', i) as string; - const encoding = this.getNodeParameter('encoding', i) as string; - const fieldName = this.getNodeParameter('fieldName', i) as string; + const encoding = this.getNodeParameter('encoding', i) as HexBase64Latin1Encoding; const clone = { ...items[i].json }; - if (clone[fieldName] === undefined) { - throw new Error(`The field ${fieldName} does not exist on the input data`); - } - //@ts-ignore - clone[fieldName] = createHash(type).update(clone[fieldName] as string).digest(encoding); + clone[dataPropertyName] = createHash(type).update(value).digest(encoding); responseData = clone; } if (action === 'hmac') { const type = this.getNodeParameter('type', i) as string; const secret = this.getNodeParameter('secret', i) as string; - const encoding = this.getNodeParameter('encoding', i) as string; - const fieldName = this.getNodeParameter('fieldName', i) as string; + const encoding = this.getNodeParameter('encoding', i) as HexBase64Latin1Encoding; const clone = { ...items[i].json }; - if (clone[fieldName] === undefined) { - throw new Error(`The field ${fieldName} does not exist on the input data`); - } - //@ts-ignore - clone[fieldName] = createHmac(type, secret).update(clone[fieldName] as string).digest(encoding); + clone[dataPropertyName] = createHmac(type, secret).update(value).digest(encoding); responseData = clone; } if (action === 'sign') { const algorithm = this.getNodeParameter('algorithm', i) as string; - const fieldName = this.getNodeParameter('fieldName', i) as string; - const encoding = this.getNodeParameter('encoding', i) as string; - const privateKey = this.getNodeParameter('privateKey', i) as IDataObject; + const encoding = this.getNodeParameter('encoding', i) as HexBase64Latin1Encoding; + const privateKey = this.getNodeParameter('privateKey', i) as string; const clone = { ...items[i].json }; - if (clone[fieldName] === undefined) { - throw new Error(`The field ${fieldName} does not exist on the input data`); - } - const sign = createSign(algorithm) - sign.write(clone[fieldName] as string); + const sign = createSign(algorithm); + sign.write(value as string); sign.end(); - //@ts-ignore const signature = sign.sign(privateKey, encoding); - clone[fieldName] = signature; + clone[dataPropertyName] = signature; responseData = clone; } if (Array.isArray(responseData)) {