Skip to content

Commit

Permalink
feat(Crypto Node): Add support for hash and hmac on binary data (#6359)
Browse files Browse the repository at this point in the history
  • Loading branch information
netroy authored Jun 2, 2023
1 parent 9dfc110 commit 406a405
Show file tree
Hide file tree
Showing 3 changed files with 168 additions and 57 deletions.
79 changes: 65 additions & 14 deletions packages/nodes-base/nodes/Crypto/Crypto.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -204,6 +236,7 @@ export class Crypto implements INodeType {
displayOptions: {
show: {
action: ['hmac'],
binaryData: [false],
},
},
type: 'string',
Expand Down Expand Up @@ -264,6 +297,7 @@ export class Crypto implements INodeType {
displayOptions: {
show: {
action: ['sign'],
binaryData: [false],
},
},
type: 'string',
Expand Down Expand Up @@ -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);
Expand All @@ -449,17 +484,33 @@ export class Crypto implements INodeType {
}
}
}
if (action === 'hash') {
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') {

if (action === 'hash' || 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;
Expand Down Expand Up @@ -489,7 +540,7 @@ export class Crypto implements INodeType {
};
}

if (item.binary !== undefined) {
if (item.binary !== undefined && !binaryProcessed) {
newItem.binary = item.binary;
}

Expand Down
23 changes: 21 additions & 2 deletions packages/nodes-base/nodes/Crypto/test/Crypto.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
Loading

0 comments on commit 406a405

Please sign in to comment.