diff --git a/packages/nodes-base/nodes/Crypto/Crypto.node.ts b/packages/nodes-base/nodes/Crypto/Crypto.node.ts index 3da4c6b72b..201ee8c04a 100644 --- a/packages/nodes-base/nodes/Crypto/Crypto.node.ts +++ b/packages/nodes-base/nodes/Crypto/Crypto.node.ts @@ -9,13 +9,17 @@ import type { INodeTypeDescription, JsonObject, } from 'n8n-workflow'; -import { deepCopy } from 'n8n-workflow'; +import { deepCopy, BINARY_ENCODING } from 'n8n-workflow'; import type { BinaryToTextEncoding } from 'crypto'; import { createHash, createHmac, createSign, getHashes, randomBytes } from 'crypto'; +import stream from 'stream'; +import { promisify } from 'util'; import { v4 as uuid } from 'uuid'; +const pipeline = promisify(stream.pipeline); + export class Crypto implements INodeType { description: INodeTypeDescription = { displayName: 'Crypto', @@ -45,15 +49,15 @@ export class Crypto implements INodeType { }, { name: 'Hash', - description: 'Hash a text in a specified format', + description: 'Hash a text or file in a specified format', value: 'hash', - action: 'Hash a text in a specified format', + action: 'Hash a text or file in a specified format', }, { name: 'Hmac', - description: 'Hmac a text in a specified format', + description: 'Hmac a text or file in a specified format', value: 'hmac', - action: 'HMAC a text in a specified format', + action: 'HMAC a text or file in a specified format', }, { name: 'Sign', @@ -107,12 +111,40 @@ export class Crypto implements INodeType { description: 'The hash type to use', required: true, }, + { + displayName: 'Binary Data', + name: 'binaryData', + type: 'boolean', + default: false, + required: true, + displayOptions: { + show: { + action: ['hash', 'hmac'], + }, + }, + description: 'Whether the data to hashed should be taken from binary field', + }, + { + displayName: 'Binary Property Name', + name: 'binaryPropertyName', + displayOptions: { + show: { + action: ['hash', 'hmac'], + binaryData: [true], + }, + }, + type: 'string', + default: 'data', + description: 'Name of the binary property which contains the input data', + required: true, + }, { displayName: 'Value', name: 'value', displayOptions: { show: { action: ['hash'], + binaryData: [false], }, }, type: 'string', @@ -204,6 +236,7 @@ export class Crypto implements INodeType { displayOptions: { show: { action: ['hmac'], + binaryData: [false], }, }, type: 'string', @@ -264,6 +297,7 @@ export class Crypto implements INodeType { displayOptions: { show: { action: ['sign'], + binaryData: [false], }, }, type: 'string', @@ -430,6 +464,7 @@ export class Crypto implements INodeType { const dataPropertyName = this.getNodeParameter('dataPropertyName', i); const value = this.getNodeParameter('value', i, '') as string; let newValue; + let binaryProcessed = false; if (action === 'generate') { const encodingType = this.getNodeParameter('encodingType', i); @@ -449,17 +484,33 @@ export class Crypto implements INodeType { } } } - if (action === 'hash') { + + if (action === 'hash' || action === 'hmac') { const type = this.getNodeParameter('type', i) as string; const encoding = this.getNodeParameter('encoding', i) as BinaryToTextEncoding; - newValue = createHash(type).update(value).digest(encoding); - } - 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 BinaryToTextEncoding; - newValue = createHmac(type, secret).update(value).digest(encoding); + const hashOrHmac = + action === 'hash' + ? createHash(type) + : createHmac(type, this.getNodeParameter('secret', i) as string); + if (this.getNodeParameter('binaryData', i)) { + const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i); + const binaryData = this.helpers.assertBinaryData(i, binaryPropertyName); + if (binaryData.id) { + const binaryStream = this.helpers.getBinaryStream(binaryData.id); + hashOrHmac.setEncoding(encoding); + await pipeline(binaryStream, hashOrHmac); + newValue = hashOrHmac.read(); + } else { + newValue = hashOrHmac + .update(Buffer.from(binaryData.data, BINARY_ENCODING)) + .digest(encoding); + } + binaryProcessed = true; + } else { + newValue = hashOrHmac.update(value).digest(encoding); + } } + if (action === 'sign') { const algorithm = this.getNodeParameter('algorithm', i) as string; const encoding = this.getNodeParameter('encoding', i) as BinaryToTextEncoding; @@ -489,7 +540,7 @@ export class Crypto implements INodeType { }; } - if (item.binary !== undefined) { + if (item.binary !== undefined && !binaryProcessed) { newItem.binary = item.binary; } diff --git a/packages/nodes-base/nodes/Crypto/test/Crypto.test.ts b/packages/nodes-base/nodes/Crypto/test/Crypto.test.ts index c0eff05306..f78e21610b 100644 --- a/packages/nodes-base/nodes/Crypto/test/Crypto.test.ts +++ b/packages/nodes-base/nodes/Crypto/test/Crypto.test.ts @@ -1,5 +1,24 @@ -import { testWorkflows, getWorkflowFilenames } from '../../../test/nodes/Helpers'; +import fs from 'fs'; +import fsPromises from 'fs/promises'; +import { Readable } from 'stream'; +import { + testWorkflows, + getWorkflowFilenames, + initBinaryDataManager, +} from '../../../test/nodes/Helpers'; const workflows = getWorkflowFilenames(__dirname); -describe('Test Crypto Node', () => testWorkflows(workflows)); +describe('Test Crypto Node', () => { + jest.mock('fast-glob', () => async () => ['/test/binary.data']); + jest.mock('fs/promises'); + fsPromises.access = async () => {}; + jest.mock('fs'); + fs.createReadStream = () => Readable.from(Buffer.from('test')) as fs.ReadStream; + + beforeEach(async () => { + await initBinaryDataManager(); + }); + + testWorkflows(workflows); +}); diff --git a/packages/nodes-base/nodes/Crypto/test/CryptoTest.workflow.json b/packages/nodes-base/nodes/Crypto/test/CryptoTest.workflow.json index 3b044757ab..4d60bd0d46 100644 --- a/packages/nodes-base/nodes/Crypto/test/CryptoTest.workflow.json +++ b/packages/nodes-base/nodes/Crypto/test/CryptoTest.workflow.json @@ -1,5 +1,5 @@ { - "name": "review node unit tests", + "name": "Crypto Test", "nodes": [ { "parameters": {}, @@ -7,41 +7,32 @@ "name": "When clicking \"Execute Workflow\"", "type": "n8n-nodes-base.manualTrigger", "typeVersion": 1, - "position": [ - -2640, - 4140 - ] + "position": [-480, 460] }, { "parameters": { - "value": "=test" + "value": "test" }, "id": "90831322-8a73-40ac-ae52-84a6504d3d95", "name": "Crypto Hash into Hex", "type": "n8n-nodes-base.crypto", "typeVersion": 1, - "position": [ - -2120, - 4140 - ] + "position": [360, 660] }, { "parameters": { - "value": "=test" + "value": "test" }, "id": "9836f128-6798-498e-8752-ba447218ce21", "name": "Crypto Hash into MD5", "type": "n8n-nodes-base.crypto", "typeVersion": 1, - "position": [ - -2120, - 3980 - ] + "position": [360, 500] }, { "parameters": { "action": "sign", - "value": "=test", + "value": "test", "algorithm": "RSA-MD4", "encoding": "base64", "privateKey": "-----BEGIN RSA PRIVATE KEY-----\nMIIBOgIBAAJBAKj34GkxFhD90vcNLYLInFEX6Ppy1tPf9Cnzj4p4WGeKLs1Pt8Qu\nKUpRKfFLfRYC9AIKjbJTWit+CqvjWYzvQwECAwEAAQJAIJLixBy2qpFoS4DSmoEm\no3qGy0t6z09AIJtH+5OeRV1be+N4cDYJKffGzDa88vQENZiRm0GRq6a+HPGQMd2k\nTQIhAKMSvzIBnni7ot/OSie2TmJLY4SwTQAevXysE2RbFDYdAiEBCUEaRQnMnbp7\n9mxDXDf6AU0cN/RPBjb9qSHDcWZHGzUCIG2Es59z8ugGrDY+pxLQnwfotadxd+Uy\nv/Ow5T0q5gIJAiEAyS4RaI9YG8EWx/2w0T67ZUVAw8eOMB6BIUg0Xcu+3okCIBOs\n/5OiPgoTdSy7bcF9IGpSE8ZgGKzgYQVZeN97YE00\n-----END RSA PRIVATE KEY-----" @@ -50,15 +41,12 @@ "name": "Crypto Sign data with RSA-MDA4", "type": "n8n-nodes-base.crypto", "typeVersion": 1, - "position": [ - -2120, - 3800 - ] + "position": [80, 860] }, { "parameters": { "action": "hmac", - "value": "=test", + "value": "test", "secret": "-----BEGIN RSA PRIVATE KEY-----|MIIBOgIBAAJBAKj34GkxFhD90vcNLYLInFEX6Ppy1tPf9Cnzj4p4WGeKLs1Pt8QuKUpRKfFLfRYC9AIKjbJTWit+CqvjWYzvQwECAwEAAQJAIJLixBy2qpFoS4DSmoEmo3qGy0t6z09AIJtH+5OeRV1be+N4cDYJKffGzDa88vQENZiRm0GRq6a+HPGQMd2kTQIhAKMSvzIBnni7ot/OSie2TmJLY4SwTQAevXysE2RbFDYdAiEBCUEaRQnMnbp79mxDXDf6AU0cN/RPBjb9qSHDcWZHGzUCIG2Es59z8ugGrDY+pxLQnwfotadxd+Uyv/Ow5T0q5gIJAiEAyS4RaI9YG8EWx/2w0T67ZUVAw8eOMB6BIUg0Xcu+3okCIBOs/5OiPgoTdSy7bcF9IGpSE8ZgGKzgYQVZeN97YE00-----END RSA PRIVATE KEY-----", "encoding": "base64" }, @@ -66,10 +54,7 @@ "name": "Crypto Hmac data with MD5", "type": "n8n-nodes-base.crypto", "typeVersion": 1, - "position": [ - -2120, - 3620 - ] + "position": [360, 320] }, { "parameters": { @@ -79,10 +64,7 @@ "name": "Crypto Generate UUID", "type": "n8n-nodes-base.crypto", "typeVersion": 1, - "position": [ - -2120, - 4320 - ] + "position": [-160, 1060] }, { "parameters": { @@ -100,10 +82,7 @@ "name": "IF", "type": "n8n-nodes-base.if", "typeVersion": 1, - "position": [ - -1900, - 4320 - ] + "position": [80, 1060] }, { "parameters": {}, @@ -111,10 +90,7 @@ "name": "No Operation, do nothing", "type": "n8n-nodes-base.noOp", "typeVersion": 1, - "position": [ - -1680, - 4300 - ] + "position": [420, 1040] }, { "parameters": { @@ -124,10 +100,40 @@ "name": "Stop and Error", "type": "n8n-nodes-base.stopAndError", "typeVersion": 1, - "position": [ - -1680, - 4500 - ] + "position": [260, 1180] + }, + { + "parameters": { + "fileSelector": "/test/binary.data" + }, + "id": "09bbc611-c2ca-4750-94a7-d1bb4fc53a57", + "name": "Read Binary Files", + "type": "n8n-nodes-base.readBinaryFiles", + "typeVersion": 1, + "position": [-160, 40] + }, + { + "parameters": { + "binaryData": true + }, + "id": "9f23080a-402d-4a82-821c-b74388cc9a26", + "name": "Crypto Hash Binary Data", + "type": "n8n-nodes-base.crypto", + "typeVersion": 1, + "position": [200, -80] + }, + { + "parameters": { + "action": "hmac", + "binaryData": true, + "secret": "-----BEGIN RSA PRIVATE KEY-----|MIIBOgIBAAJBAKj34GkxFhD90vcNLYLInFEX6Ppy1tPf9Cnzj4p4WGeKLs1Pt8QuKUpRKfFLfRYC9AIKjbJTWit+CqvjWYzvQwECAwEAAQJAIJLixBy2qpFoS4DSmoEmo3qGy0t6z09AIJtH+5OeRV1be+N4cDYJKffGzDa88vQENZiRm0GRq6a+HPGQMd2kTQIhAKMSvzIBnni7ot/OSie2TmJLY4SwTQAevXysE2RbFDYdAiEBCUEaRQnMnbp79mxDXDf6AU0cN/RPBjb9qSHDcWZHGzUCIG2Es59z8ugGrDY+pxLQnwfotadxd+Uyv/Ow5T0q5gIJAiEAyS4RaI9YG8EWx/2w0T67ZUVAw8eOMB6BIUg0Xcu+3okCIBOs/5OiPgoTdSy7bcF9IGpSE8ZgGKzgYQVZeN97YE00-----END RSA PRIVATE KEY-----", + "encoding": "base64" + }, + "id": "43d5ffa2-9c95-4287-b582-b912071a05c1", + "name": "Crypto Hmac Binary Data", + "type": "n8n-nodes-base.crypto", + "typeVersion": 1, + "position": [200, 100] } ], "pinData": { @@ -158,6 +164,20 @@ "data": "BBXLTeT2o/R6oy5H69Yh7w==" } } + ], + "Crypto Hash Binary Data": [ + { + "json": { + "data": "098f6bcd4621d373cade4e832627b4f6" + } + } + ], + "Crypto Hmac Binary Data": [ + { + "json": { + "data": "BBXLTeT2o/R6oy5H69Yh7w==" + } + } ] }, "connections": { @@ -188,6 +208,11 @@ "node": "Crypto Generate UUID", "type": "main", "index": 0 + }, + { + "node": "Read Binary Files", + "type": "main", + "index": 0 } ] ] @@ -225,6 +250,22 @@ "main": [ [] ] + }, + "Read Binary Files": { + "main": [ + [ + { + "node": "Crypto Hash Binary Data", + "type": "main", + "index": 0 + }, + { + "node": "Crypto Hmac Binary Data", + "type": "main", + "index": 0 + } + ] + ] } }, "active": false,