@limetech/n8n-nodes-lime 3.6.3 → 3.6.4-dev.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 +7 -0
- package/credentials/LimeCrmApi.credentials.ts +0 -14
- 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} +3 -0
- package/dist/nodes/crypto.js +58 -0
- package/dist/nodes/crypto.js.map +1 -0
- package/dist/nodes/lime-crm/LimeCrmTrigger.node.js +33 -30
- 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/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 +2 -2
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/nodes/{common.ts → crypto.ts} +52 -15
- package/nodes/lime-crm/LimeCrmTrigger.node.ts +48 -44
- package/nodes/lime-crm/models/webhook.ts +1 -1
- package/nodes/lime-crm/transport/webhooks.ts +7 -7
- package/nodes/lime-forms/LimeFormsTrigger.node.ts +1 -1
- package/package.json +1 -1
- package/dist/nodes/common.js +0 -29
- package/dist/nodes/common.js.map +0 -1
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
createCipheriv,
|
|
3
|
+
createDecipheriv,
|
|
4
|
+
createHmac,
|
|
5
|
+
hkdfSync,
|
|
6
|
+
randomBytes,
|
|
7
|
+
} from 'node:crypto';
|
|
2
8
|
import { NodeOperationError, INode } from 'n8n-workflow';
|
|
3
9
|
|
|
10
|
+
const ENCRYPTION_KEY_ENV = 'N8N_ENCRYPTION_KEY';
|
|
11
|
+
const HKDF_INFO = 'lime-webhook-secret';
|
|
12
|
+
|
|
4
13
|
/**
|
|
5
14
|
* Generate an HMAC SHA-256 hash for the given data using the provided key.
|
|
6
15
|
*
|
|
@@ -57,20 +66,7 @@ export const verifyRequest = (
|
|
|
57
66
|
webhookSecret: string,
|
|
58
67
|
data: Buffer
|
|
59
68
|
): void => {
|
|
60
|
-
if (!
|
|
61
|
-
throw new NodeOperationError(
|
|
62
|
-
node,
|
|
63
|
-
'Webhook secret and lime signature are missing!'
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
if (!webhookSecret && limeSignature) {
|
|
67
|
-
throw new NodeOperationError(
|
|
68
|
-
node,
|
|
69
|
-
'Webhook authentication failed, secret key is missing while signature is present!'
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (!limeSignature && webhookSecret) {
|
|
69
|
+
if (!limeSignature) {
|
|
74
70
|
throw new NodeOperationError(
|
|
75
71
|
node,
|
|
76
72
|
'Webhook authentication failed, signature key is missing while secret is present!'
|
|
@@ -85,3 +81,44 @@ export const verifyRequest = (
|
|
|
85
81
|
);
|
|
86
82
|
}
|
|
87
83
|
};
|
|
84
|
+
|
|
85
|
+
export function getMasterKey(): string {
|
|
86
|
+
const key = process.env[ENCRYPTION_KEY_ENV];
|
|
87
|
+
if (!key) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
`${ENCRYPTION_KEY_ENV} must be set to manage Lime CRM webhooks`
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
return key;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function encryptSecret(plaintext: string): string {
|
|
96
|
+
const salt = randomBytes(16);
|
|
97
|
+
const iv = randomBytes(12);
|
|
98
|
+
const key = Buffer.from(
|
|
99
|
+
hkdfSync('sha256', getMasterKey(), salt, HKDF_INFO, 32)
|
|
100
|
+
);
|
|
101
|
+
const cipher = createCipheriv('aes-256-gcm', key, iv);
|
|
102
|
+
const ct = Buffer.concat([
|
|
103
|
+
cipher.update(plaintext, 'utf8'),
|
|
104
|
+
cipher.final(),
|
|
105
|
+
]);
|
|
106
|
+
const tag = cipher.getAuthTag();
|
|
107
|
+
return Buffer.concat([salt, iv, tag, ct]).toString('base64');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function decryptSecret(blob: string): string {
|
|
111
|
+
const buf = Buffer.from(blob, 'base64');
|
|
112
|
+
const salt = buf.subarray(0, 16);
|
|
113
|
+
const iv = buf.subarray(16, 28);
|
|
114
|
+
const tag = buf.subarray(28, 44);
|
|
115
|
+
const ct = buf.subarray(44);
|
|
116
|
+
const key = Buffer.from(
|
|
117
|
+
hkdfSync('sha256', getMasterKey(), salt, HKDF_INFO, 32)
|
|
118
|
+
);
|
|
119
|
+
const decipher = createDecipheriv('aes-256-gcm', key, iv);
|
|
120
|
+
decipher.setAuthTag(tag);
|
|
121
|
+
return Buffer.concat([decipher.update(ct), decipher.final()]).toString(
|
|
122
|
+
'utf8'
|
|
123
|
+
);
|
|
124
|
+
}
|
|
@@ -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,43 @@ export class LimeCrmTrigger implements INodeType {
|
|
|
202
204
|
});
|
|
203
205
|
}
|
|
204
206
|
|
|
205
|
-
if (existingSubscriptionResponse.data.length
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
const createSubscriptionResponse = await createSubscription(
|
|
215
|
-
this,
|
|
216
|
-
webhookCreateData
|
|
217
|
-
);
|
|
218
|
-
|
|
219
|
-
if (!createSubscriptionResponse.success) {
|
|
220
|
-
throw new NodeApiError(this.getNode(), {
|
|
221
|
-
message:
|
|
222
|
-
createSubscriptionResponse.data.error.message,
|
|
223
|
-
});
|
|
207
|
+
if (existingSubscriptionResponse.data.length > 0) {
|
|
208
|
+
for (const subscription of existingSubscriptionResponse.data) {
|
|
209
|
+
Logger.info(
|
|
210
|
+
'Deleting existing Lime CRM webhook with ID: ' +
|
|
211
|
+
subscription.id
|
|
212
|
+
);
|
|
213
|
+
await deleteSubscription(this, subscription.id);
|
|
224
214
|
}
|
|
215
|
+
}
|
|
225
216
|
|
|
226
|
-
|
|
227
|
-
|
|
217
|
+
const rawSecret = randomBytes(32).toString('hex');
|
|
218
|
+
webhook.data.webhookSecret = encryptSecret(rawSecret);
|
|
219
|
+
const webhookCreateData = {
|
|
220
|
+
...webhook,
|
|
221
|
+
secret: rawSecret,
|
|
222
|
+
};
|
|
228
223
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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;
|
|
224
|
+
const createSubscriptionResponse = await createSubscription(
|
|
225
|
+
this,
|
|
226
|
+
webhookCreateData
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
if (!createSubscriptionResponse.success) {
|
|
230
|
+
throw new NodeApiError(this.getNode(), {
|
|
231
|
+
message: createSubscriptionResponse.data.error.message,
|
|
232
|
+
});
|
|
244
233
|
}
|
|
234
|
+
|
|
235
|
+
const subscriptionId = createSubscriptionResponse.data.id;
|
|
236
|
+
const events = createSubscriptionResponse.data.events;
|
|
237
|
+
|
|
238
|
+
webhook.data.webhookId = subscriptionId;
|
|
239
|
+
webhook.data.webhookEvents = events;
|
|
240
|
+
Logger.info(
|
|
241
|
+
`Webhook with URL ${webhook.url}, ID ${subscriptionId} and events ${events} created!`
|
|
242
|
+
);
|
|
243
|
+
return true;
|
|
245
244
|
},
|
|
246
245
|
|
|
247
246
|
async delete(this: IHookFunctions): Promise<boolean> {
|
|
@@ -252,7 +251,7 @@ export class LimeCrmTrigger implements INodeType {
|
|
|
252
251
|
);
|
|
253
252
|
if (webhook.data.webhookId !== undefined) {
|
|
254
253
|
try {
|
|
255
|
-
await deleteSubscription(this, webhook);
|
|
254
|
+
await deleteSubscription(this, webhook.data.webhookId);
|
|
256
255
|
} catch {
|
|
257
256
|
Logger.error(
|
|
258
257
|
`Failed to delete webhook with ID: ${webhook.data.webhookId}`,
|
|
@@ -295,11 +294,16 @@ export class LimeCrmTrigger implements INodeType {
|
|
|
295
294
|
const webhook = getWebhook(this);
|
|
296
295
|
Logger.info('Webhook received. Starting webhook processing...', {
|
|
297
296
|
...webhook.context,
|
|
297
|
+
...webhook.data,
|
|
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();
|
|
@@ -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';
|
package/package.json
CHANGED
package/dist/nodes/common.js
DELETED
|
@@ -1,29 +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
|
-
if (!webhookSecret && !limeSignature) {
|
|
15
|
-
throw new n8n_workflow_1.NodeOperationError(node, 'Webhook secret and lime signature are missing!');
|
|
16
|
-
}
|
|
17
|
-
if (!webhookSecret && limeSignature) {
|
|
18
|
-
throw new n8n_workflow_1.NodeOperationError(node, 'Webhook authentication failed, secret key is missing while signature is present!');
|
|
19
|
-
}
|
|
20
|
-
if (!limeSignature && webhookSecret) {
|
|
21
|
-
throw new n8n_workflow_1.NodeOperationError(node, 'Webhook authentication failed, signature key is missing while secret is present!');
|
|
22
|
-
}
|
|
23
|
-
const expectedHmac = generateHmac(webhookSecret, data);
|
|
24
|
-
if (expectedHmac !== limeSignature) {
|
|
25
|
-
throw new n8n_workflow_1.NodeOperationError(node, 'Webhook authentication failed, signatures do not match');
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
exports.verifyRequest = verifyRequest;
|
|
29
|
-
//# 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,6CAAyC;AACzC,+CAAyD;AAYzD,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,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;AAjCW,QAAA,aAAa,iBAiCxB"}
|