@limetech/n8n-nodes-lime 3.7.0-dev.2 → 3.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -9
- package/credentials/LimeCrmApi.credentials.ts +0 -19
- package/dist/credentials/LimeCrmApi.credentials.js +0 -16
- package/dist/credentials/LimeCrmApi.credentials.js.map +1 -1
- package/dist/nodes/{common.d.ts → crypto.d.ts} +2 -0
- package/dist/nodes/crypto.js +57 -0
- package/dist/nodes/crypto.js.map +1 -0
- package/dist/nodes/lime-crm/LimeCrmTrigger.node.js +33 -31
- package/dist/nodes/lime-crm/LimeCrmTrigger.node.js.map +1 -1
- package/dist/nodes/lime-crm/models/webhook.d.ts +4 -1
- package/dist/nodes/lime-crm/transport/commons.js +4 -2
- package/dist/nodes/lime-crm/transport/commons.js.map +1 -1
- package/dist/nodes/lime-crm/transport/webhooks.d.ts +2 -2
- package/dist/nodes/lime-crm/transport/webhooks.js +5 -5
- package/dist/nodes/lime-crm/transport/webhooks.js.map +1 -1
- package/dist/nodes/lime-forms/LimeFormsTrigger.node.js +3 -3
- package/dist/nodes/lime-forms/LimeFormsTrigger.node.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/nodes/{common.ts → crypto.ts} +71 -26
- package/nodes/lime-crm/LimeCrmTrigger.node.ts +47 -43
- package/nodes/lime-crm/models/webhook.ts +1 -1
- package/nodes/lime-crm/transport/commons.ts +2 -0
- package/nodes/lime-crm/transport/webhooks.ts +7 -7
- package/nodes/lime-forms/LimeFormsTrigger.node.ts +2 -2
- package/package.json +1 -1
- package/dist/nodes/common.js +0 -36
- package/dist/nodes/common.js.map +0 -1
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
createCipheriv,
|
|
3
|
+
createDecipheriv,
|
|
4
|
+
createHmac,
|
|
5
|
+
hkdfSync,
|
|
6
|
+
randomBytes,
|
|
7
|
+
} from 'node:crypto';
|
|
8
|
+
import { NodeOperationError, INode } from 'n8n-workflow';
|
|
9
|
+
|
|
10
|
+
const ENCRYPTION_KEY_ENV = 'N8N_ENCRYPTION_KEY';
|
|
11
|
+
const HKDF_INFO = 'lime-webhook-secret';
|
|
3
12
|
|
|
4
13
|
/**
|
|
5
14
|
* Generate an HMAC SHA-256 hash for the given data using the provided key.
|
|
@@ -57,30 +66,7 @@ export const verifyRequest = (
|
|
|
57
66
|
webhookSecret: string,
|
|
58
67
|
data: Buffer
|
|
59
68
|
): void => {
|
|
60
|
-
|
|
61
|
-
.update(webhookSecret)
|
|
62
|
-
.digest('hex')
|
|
63
|
-
.slice(0, 16);
|
|
64
|
-
Logger.info(
|
|
65
|
-
`Webhook secret for node ${node.id} ${node.name} fingerprint at request time`,
|
|
66
|
-
{
|
|
67
|
-
secretFingerprint,
|
|
68
|
-
}
|
|
69
|
-
);
|
|
70
|
-
if (!webhookSecret && !limeSignature) {
|
|
71
|
-
throw new NodeOperationError(
|
|
72
|
-
node,
|
|
73
|
-
'Webhook secret and lime signature are missing!'
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
if (!webhookSecret && limeSignature) {
|
|
77
|
-
throw new NodeOperationError(
|
|
78
|
-
node,
|
|
79
|
-
'Webhook authentication failed, secret key is missing while signature is present!'
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (!limeSignature && webhookSecret) {
|
|
69
|
+
if (!limeSignature) {
|
|
84
70
|
throw new NodeOperationError(
|
|
85
71
|
node,
|
|
86
72
|
'Webhook authentication failed, signature key is missing while secret is present!'
|
|
@@ -95,3 +81,62 @@ export const verifyRequest = (
|
|
|
95
81
|
);
|
|
96
82
|
}
|
|
97
83
|
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Retrieves the master encryption key from the environment variable.
|
|
87
|
+
* Throws an error if the environment variable is not set.
|
|
88
|
+
*
|
|
89
|
+
* @return The master encryption key.
|
|
90
|
+
*/
|
|
91
|
+
const getMasterKey = (): string => {
|
|
92
|
+
const key = process.env[ENCRYPTION_KEY_ENV];
|
|
93
|
+
if (!key) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
`${ENCRYPTION_KEY_ENV} must be set to manage Lime CRM webhooks`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
return key;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Encrypts a plaintext string using AES-256-GCM with a derived key based on HKDF.
|
|
103
|
+
*
|
|
104
|
+
* @param plaintext - The plain text string to be encrypted.
|
|
105
|
+
* @return The encrypted string encoded in base64 format.
|
|
106
|
+
*/
|
|
107
|
+
export const encryptSecret = (plaintext: string): string => {
|
|
108
|
+
const salt = randomBytes(16);
|
|
109
|
+
const iv = randomBytes(12);
|
|
110
|
+
const key = Buffer.from(
|
|
111
|
+
hkdfSync('sha256', getMasterKey(), salt, HKDF_INFO, 32)
|
|
112
|
+
);
|
|
113
|
+
const cipher = createCipheriv('aes-256-gcm', key, iv);
|
|
114
|
+
const ct = Buffer.concat([
|
|
115
|
+
cipher.update(plaintext, 'utf8'),
|
|
116
|
+
cipher.final(),
|
|
117
|
+
]);
|
|
118
|
+
const tag = cipher.getAuthTag();
|
|
119
|
+
return Buffer.concat([salt, iv, tag, ct]).toString('base64');
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Decrypts a Base64-encoded secret using AES-256-GCM with key derivation.
|
|
124
|
+
*
|
|
125
|
+
* @param blob - The Base64-encoded string containing the encrypted data, including salt, IV, authentication tag, and ciphertext.
|
|
126
|
+
* @return The decrypted data as a UTF-8 string.
|
|
127
|
+
*/
|
|
128
|
+
export const decryptSecret = (blob: string): string => {
|
|
129
|
+
const buf = Buffer.from(blob, 'base64');
|
|
130
|
+
const salt = buf.subarray(0, 16);
|
|
131
|
+
const iv = buf.subarray(16, 28);
|
|
132
|
+
const tag = buf.subarray(28, 44);
|
|
133
|
+
const ct = buf.subarray(44);
|
|
134
|
+
const key = Buffer.from(
|
|
135
|
+
hkdfSync('sha256', getMasterKey(), salt, HKDF_INFO, 32)
|
|
136
|
+
);
|
|
137
|
+
const decipher = createDecipheriv('aes-256-gcm', key, iv);
|
|
138
|
+
decipher.setAuthTag(tag);
|
|
139
|
+
return Buffer.concat([decipher.update(ct), decipher.final()]).toString(
|
|
140
|
+
'utf8'
|
|
141
|
+
);
|
|
142
|
+
};
|
|
@@ -19,9 +19,11 @@ import {
|
|
|
19
19
|
getSubscription,
|
|
20
20
|
listSubscriptionsWithExistingData,
|
|
21
21
|
} from './transport';
|
|
22
|
-
import { createHash } from 'node:crypto';
|
|
22
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
23
|
+
|
|
24
|
+
import { decryptSecret, encryptSecret } from '../crypto';
|
|
23
25
|
import { getWebhook, handleWorkflowError } from './utils';
|
|
24
|
-
import { verifyRequest } from '../
|
|
26
|
+
import { verifyRequest } from '../crypto';
|
|
25
27
|
|
|
26
28
|
/**
|
|
27
29
|
* Trigger node for handling incoming webhooks from **Lime CRM**.
|
|
@@ -165,7 +167,7 @@ export class LimeCrmTrigger implements INodeType {
|
|
|
165
167
|
}
|
|
166
168
|
|
|
167
169
|
try {
|
|
168
|
-
await getSubscription(this, webhook);
|
|
170
|
+
await getSubscription(this, webhook.data.webhookId);
|
|
169
171
|
} catch (error) {
|
|
170
172
|
if (error.cause?.status === 404) {
|
|
171
173
|
delete webhook.data.webhookId;
|
|
@@ -202,46 +204,44 @@ export class LimeCrmTrigger implements INodeType {
|
|
|
202
204
|
});
|
|
203
205
|
}
|
|
204
206
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
secret: credentials.webhookSecret as string,
|
|
212
|
-
};
|
|
207
|
+
const rawSecret = randomBytes(32).toString('hex');
|
|
208
|
+
webhook.data.webhookSecret = encryptSecret(rawSecret);
|
|
209
|
+
const webhookCreateData = {
|
|
210
|
+
...webhook,
|
|
211
|
+
secret: rawSecret,
|
|
212
|
+
};
|
|
213
213
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
214
|
+
const createSubscriptionResponse = await createSubscription(
|
|
215
|
+
this,
|
|
216
|
+
webhookCreateData
|
|
217
|
+
);
|
|
218
218
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
219
|
+
if (!createSubscriptionResponse.success) {
|
|
220
|
+
throw new NodeApiError(this.getNode(), {
|
|
221
|
+
message: createSubscriptionResponse.data.error.message,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Delete existing duplicated webhooks if a new one was successfully created
|
|
226
|
+
if (existingSubscriptionResponse.data.length > 0) {
|
|
227
|
+
for (const subscription of existingSubscriptionResponse.data) {
|
|
228
|
+
Logger.info(
|
|
229
|
+
'Deleting existing Lime CRM webhook with ID: ' +
|
|
230
|
+
subscription.id
|
|
231
|
+
);
|
|
232
|
+
await deleteSubscription(this, subscription.id);
|
|
224
233
|
}
|
|
234
|
+
}
|
|
225
235
|
|
|
226
|
-
|
|
227
|
-
|
|
236
|
+
const subscriptionId = createSubscriptionResponse.data.id;
|
|
237
|
+
const events = createSubscriptionResponse.data.events;
|
|
228
238
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
} else {
|
|
236
|
-
const limeWebhook = existingSubscriptionResponse.data[0];
|
|
237
|
-
webhook.data.webhookId = limeWebhook.id;
|
|
238
|
-
webhook.data.webhookEvents = limeWebhook.events;
|
|
239
|
-
Logger.info(
|
|
240
|
-
`Webhook with URL ${webhook.url}, and events ${webhook.events} exists in Lime.
|
|
241
|
-
ID ${limeWebhook.id} set up to the N8N data.`
|
|
242
|
-
);
|
|
243
|
-
return true;
|
|
244
|
-
}
|
|
239
|
+
webhook.data.webhookId = subscriptionId;
|
|
240
|
+
webhook.data.webhookEvents = events;
|
|
241
|
+
Logger.info(
|
|
242
|
+
`Webhook with URL ${webhook.url}, ID ${subscriptionId} and events ${events} created!`
|
|
243
|
+
);
|
|
244
|
+
return true;
|
|
245
245
|
},
|
|
246
246
|
|
|
247
247
|
async delete(this: IHookFunctions): Promise<boolean> {
|
|
@@ -252,7 +252,7 @@ export class LimeCrmTrigger implements INodeType {
|
|
|
252
252
|
);
|
|
253
253
|
if (webhook.data.webhookId !== undefined) {
|
|
254
254
|
try {
|
|
255
|
-
await deleteSubscription(this, webhook);
|
|
255
|
+
await deleteSubscription(this, webhook.data.webhookId);
|
|
256
256
|
} catch {
|
|
257
257
|
Logger.error(
|
|
258
258
|
`Failed to delete webhook with ID: ${webhook.data.webhookId}`,
|
|
@@ -296,10 +296,14 @@ export class LimeCrmTrigger implements INodeType {
|
|
|
296
296
|
Logger.info('Webhook received. Starting webhook processing...', {
|
|
297
297
|
...webhook.context,
|
|
298
298
|
});
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
299
|
+
const encryptedSecret = webhook.data.webhookSecret;
|
|
300
|
+
if (!encryptedSecret) {
|
|
301
|
+
throw new NodeOperationError(
|
|
302
|
+
this.getNode(),
|
|
303
|
+
'Webhook is not registered: missing secret in static data'
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
const webhookSecret = decryptSecret(encryptedSecret);
|
|
303
307
|
const requestObject = this.getRequestObject();
|
|
304
308
|
const headerData = this.getHeaderData();
|
|
305
309
|
const bodyData = this.getBodyData();
|
|
@@ -116,9 +116,11 @@ export async function callLimeApi<T>(
|
|
|
116
116
|
data: response,
|
|
117
117
|
};
|
|
118
118
|
} catch (error) {
|
|
119
|
+
const errorBody = error?.cause?.error ?? error?.cause?.body;
|
|
119
120
|
const errorContext: WorkflowErrorContext = {
|
|
120
121
|
message: error instanceof Error ? error.message : String(error),
|
|
121
122
|
status: error?.cause?.status,
|
|
123
|
+
...(errorBody != null && { error: errorBody }),
|
|
122
124
|
metadata: {
|
|
123
125
|
...options.requestOptions,
|
|
124
126
|
...options.errorMetadata,
|
|
@@ -35,7 +35,7 @@ export interface ApiResponseWebhook {
|
|
|
35
35
|
* Retrieve details of a specific subscription from Lime CRM.
|
|
36
36
|
*
|
|
37
37
|
* @param nodeContext - The n8n node execution context
|
|
38
|
-
* @param
|
|
38
|
+
* @param webhookId - Id of a webhook containing subscription details
|
|
39
39
|
*
|
|
40
40
|
* @returns The subscription information from Lime CRM.
|
|
41
41
|
*
|
|
@@ -44,11 +44,11 @@ export interface ApiResponseWebhook {
|
|
|
44
44
|
*/
|
|
45
45
|
export async function getSubscription(
|
|
46
46
|
nodeContext: IAllExecuteFunctions,
|
|
47
|
-
|
|
47
|
+
webhookId: string
|
|
48
48
|
): Promise<APIResponse<ApiResponseWebhook>> {
|
|
49
49
|
return await callLimeApi(nodeContext, {
|
|
50
50
|
method: 'GET',
|
|
51
|
-
url: `${SUBSCRIPTION_URL}${
|
|
51
|
+
url: `${SUBSCRIPTION_URL}${webhookId}`,
|
|
52
52
|
});
|
|
53
53
|
}
|
|
54
54
|
|
|
@@ -113,7 +113,7 @@ export async function createSubscription(
|
|
|
113
113
|
* Delete a webhook subscription from Lime CRM.
|
|
114
114
|
*
|
|
115
115
|
* @param nodeContext - The n8n node execution context
|
|
116
|
-
* @param
|
|
116
|
+
* @param webhookId - ID of a webhook that should be deleted
|
|
117
117
|
*
|
|
118
118
|
* @returns Response indicating success or failure of the deletion.
|
|
119
119
|
*
|
|
@@ -122,13 +122,13 @@ export async function createSubscription(
|
|
|
122
122
|
*/
|
|
123
123
|
export async function deleteSubscription(
|
|
124
124
|
nodeContext: IAllExecuteFunctions,
|
|
125
|
-
|
|
125
|
+
webhookId: string
|
|
126
126
|
): Promise<APIResponse<void>> {
|
|
127
127
|
return await callLimeApi(nodeContext, {
|
|
128
128
|
method: 'DELETE',
|
|
129
|
-
url: `${SUBSCRIPTION_URL}${
|
|
129
|
+
url: `${SUBSCRIPTION_URL}${webhookId}/`,
|
|
130
130
|
errorMetadata: {
|
|
131
|
-
id:
|
|
131
|
+
id: webhookId,
|
|
132
132
|
},
|
|
133
133
|
});
|
|
134
134
|
}
|
|
@@ -22,7 +22,7 @@ import { ObservableActionType } from './types/enums/ObservableAction';
|
|
|
22
22
|
import { ObservableType } from './types/enums/ObservableType';
|
|
23
23
|
import { FORMS_API_CREDENTIALS_NAME } from '../../credentials';
|
|
24
24
|
import { getWorkflowUrl } from './utils/workflow';
|
|
25
|
-
import { verifyHmac } from '../
|
|
25
|
+
import { verifyHmac } from '../crypto';
|
|
26
26
|
import { handleWorkflowError } from '../errorHandling';
|
|
27
27
|
|
|
28
28
|
const FORMS_OBSERVABLE_WEBHOOK_NAME_PREFIX = 'N8N';
|
|
@@ -242,7 +242,7 @@ export class LimeFormsTrigger implements INodeType {
|
|
|
242
242
|
'Invalid signature'
|
|
243
243
|
);
|
|
244
244
|
}
|
|
245
|
-
returnData.push(body);
|
|
245
|
+
returnData.push(body.data as IDataObject);
|
|
246
246
|
} catch (error) {
|
|
247
247
|
const response = handleWorkflowError(this.getNode(), {
|
|
248
248
|
message: error.message,
|
package/package.json
CHANGED
package/dist/nodes/common.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.verifyRequest = void 0;
|
|
4
|
-
exports.verifyHmac = verifyHmac;
|
|
5
|
-
const node_crypto_1 = require("node:crypto");
|
|
6
|
-
const n8n_workflow_1 = require("n8n-workflow");
|
|
7
|
-
function generateHmac(key, data) {
|
|
8
|
-
return 'sha256=' + (0, node_crypto_1.createHmac)('sha256', key).update(data).digest('hex');
|
|
9
|
-
}
|
|
10
|
-
function verifyHmac(key, data, comparedHmac) {
|
|
11
|
-
return generateHmac(key, data) === comparedHmac;
|
|
12
|
-
}
|
|
13
|
-
const verifyRequest = (node, limeSignature, webhookSecret, data) => {
|
|
14
|
-
const secretFingerprint = (0, node_crypto_1.createHash)('sha256')
|
|
15
|
-
.update(webhookSecret)
|
|
16
|
-
.digest('hex')
|
|
17
|
-
.slice(0, 16);
|
|
18
|
-
n8n_workflow_1.LoggerProxy.info(`Webhook secret for node ${node.id} ${node.name} fingerprint at request time`, {
|
|
19
|
-
secretFingerprint,
|
|
20
|
-
});
|
|
21
|
-
if (!webhookSecret && !limeSignature) {
|
|
22
|
-
throw new n8n_workflow_1.NodeOperationError(node, 'Webhook secret and lime signature are missing!');
|
|
23
|
-
}
|
|
24
|
-
if (!webhookSecret && limeSignature) {
|
|
25
|
-
throw new n8n_workflow_1.NodeOperationError(node, 'Webhook authentication failed, secret key is missing while signature is present!');
|
|
26
|
-
}
|
|
27
|
-
if (!limeSignature && webhookSecret) {
|
|
28
|
-
throw new n8n_workflow_1.NodeOperationError(node, 'Webhook authentication failed, signature key is missing while secret is present!');
|
|
29
|
-
}
|
|
30
|
-
const expectedHmac = generateHmac(webhookSecret, data);
|
|
31
|
-
if (expectedHmac !== limeSignature) {
|
|
32
|
-
throw new n8n_workflow_1.NodeOperationError(node, 'Webhook authentication failed, signatures do not match');
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
exports.verifyRequest = verifyRequest;
|
|
36
|
-
//# sourceMappingURL=common.js.map
|
package/dist/nodes/common.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"common.js","sourceRoot":"","sources":["../../nodes/common.ts"],"names":[],"mappings":";;;AA6BA,gCAMC;AAnCD,6CAAqD;AACrD,+CAAgF;AAYhF,SAAS,YAAY,CAAC,GAAW,EAAE,IAAY;IAC3C,OAAO,SAAS,GAAG,IAAA,wBAAU,EAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC5E,CAAC;AAcD,SAAgB,UAAU,CACtB,GAAW,EACX,IAAY,EACZ,YAAoB;IAEpB,OAAO,YAAY,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,YAAY,CAAC;AACpD,CAAC;AAkBM,MAAM,aAAa,GAAG,CACzB,IAAW,EACX,aAAqB,EACrB,aAAqB,EACrB,IAAY,EACR,EAAE;IACN,MAAM,iBAAiB,GAAG,IAAA,wBAAU,EAAC,QAAQ,CAAC;SACzC,MAAM,CAAC,aAAa,CAAC;SACrB,MAAM,CAAC,KAAK,CAAC;SACb,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAClB,0BAAM,CAAC,IAAI,CACP,2BAA2B,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,IAAI,8BAA8B,EAC7E;QACI,iBAAiB;KACpB,CACJ,CAAC;IACF,IAAI,CAAC,aAAa,IAAI,CAAC,aAAa,EAAE,CAAC;QACnC,MAAM,IAAI,iCAAkB,CACxB,IAAI,EACJ,gDAAgD,CACnD,CAAC;IACN,CAAC;IACD,IAAI,CAAC,aAAa,IAAI,aAAa,EAAE,CAAC;QAClC,MAAM,IAAI,iCAAkB,CACxB,IAAI,EACJ,kFAAkF,CACrF,CAAC;IACN,CAAC;IAED,IAAI,CAAC,aAAa,IAAI,aAAa,EAAE,CAAC;QAClC,MAAM,IAAI,iCAAkB,CACxB,IAAI,EACJ,kFAAkF,CACrF,CAAC;IACN,CAAC;IAED,MAAM,YAAY,GAAG,YAAY,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;IACvD,IAAI,YAAY,KAAK,aAAa,EAAE,CAAC;QACjC,MAAM,IAAI,iCAAkB,CACxB,IAAI,EACJ,wDAAwD,CAC3D,CAAC;IACN,CAAC;AACL,CAAC,CAAC;AA3CW,QAAA,aAAa,iBA2CxB"}
|