mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-23 10:32:17 -08:00
feat(Crypto Node): Add support for hash and hmac on binary data (#6359)
This commit is contained in:
parent
9dfc11037b
commit
406a405dd1
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue