@limetech/n8n-nodes-lime 3.10.1 → 3.10.3

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.
@@ -83,7 +83,7 @@ interface LimeAPIArguments {
83
83
  [key: string]: unknown;
84
84
  };
85
85
  json?: boolean;
86
- errorMetadata?: Record<string, unknown>;
86
+ errorMetadata?: JsonObject;
87
87
  }
88
88
 
89
89
  /**
@@ -106,7 +106,7 @@ export async function callLimeApi<T>(
106
106
  const { headers: callerHeaders, ...restRequestOptions } =
107
107
  options.requestOptions ?? {};
108
108
  const response =
109
- await nodeContext.helpers.requestWithAuthentication.call(
109
+ await nodeContext.helpers.httpRequestWithAuthentication.call(
110
110
  nodeContext,
111
111
  LIME_CRM_API_CREDENTIAL_KEY,
112
112
  {
@@ -126,15 +126,16 @@ export async function callLimeApi<T>(
126
126
  data: response,
127
127
  };
128
128
  } catch (error) {
129
- const errorBody = error?.cause?.error ?? error?.cause?.body;
129
+ const apiBody = error.context?.data;
130
130
  const errorContext: WorkflowErrorContext = {
131
- message: error instanceof Error ? error.message : String(error),
132
- status: error?.cause?.status,
133
- ...(errorBody != null && { error: errorBody }),
131
+ message: error.description
132
+ ? `${error.message}. ${error.description}`
133
+ : error.message,
134
+ status: error.httpCode ?? undefined,
134
135
  metadata: {
135
- ...options.requestOptions,
136
136
  ...options.errorMetadata,
137
- } as JsonObject,
137
+ },
138
+ ...(apiBody != null && { error: apiBody }),
138
139
  };
139
140
  return handleWorkflowError(nodeContext.getNode(), errorContext, true);
140
141
  }
@@ -22,8 +22,9 @@ 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 '../crypto';
25
+ import { decryptSecret, encryptSecret, verifyHmac } from '../crypto';
26
26
  import { handleWorkflowError } from '../errorHandling';
27
+ import { randomBytes } from 'node:crypto';
27
28
 
28
29
  const FORMS_OBSERVABLE_WEBHOOK_NAME_PREFIX = 'N8N';
29
30
 
@@ -131,22 +132,26 @@ export class LimeFormsTrigger implements INodeType {
131
132
  },
132
133
 
133
134
  async create(this: IHookFunctions): Promise<boolean> {
134
- const credentials = await this.getCredentials(
135
- FORMS_API_CREDENTIALS_NAME
136
- );
135
+ const webhookData = this.getWorkflowStaticData('node');
136
+ // Generate a per-webhook secret, keep an encrypted copy in the
137
+ // workflow static data and only ever hand the plaintext to Lime
138
+ // Forms. The secret is decrypted on each incoming request to
139
+ // validate the signature. See {@link encryptSecret}.
140
+ const rawSecret = randomBytes(32).toString('hex');
141
+ webhookData.webhookSecret = encryptSecret(rawSecret);
142
+
137
143
  const data = {
138
144
  name: `${FORMS_OBSERVABLE_WEBHOOK_NAME_PREFIX}: ${this.getNodeParameter('name')}`,
139
145
  observableType: ObservableType.FORM,
140
146
  observableId: this.getNodeParameter('formId'),
141
147
  action: ObservableActionType.FORM_SUBMITTED,
142
148
  webhookUrl: this.getNodeWebhookUrl('default'),
143
- secret: credentials.webhookSecret as string,
149
+ secret: rawSecret,
144
150
  workflowUrl: getWorkflowUrl(
145
151
  this.getNode(),
146
152
  this.getWorkflow().id
147
153
  ),
148
154
  };
149
- const webhookData = this.getWorkflowStaticData('node');
150
155
 
151
156
  try {
152
157
  const response =
@@ -164,22 +169,41 @@ export class LimeFormsTrigger implements INodeType {
164
169
 
165
170
  return true;
166
171
  } catch (error) {
167
- // 409 Indicates duplicate exists
172
+ // 409 indicates a duplicate webhook already exists in Lime
173
+ // Forms. Its secret was set on an earlier registration and
174
+ // is unknown to us, so reusing it would resurrect the
175
+ // "signatures do not match" failures. Delete the stale
176
+ // webhook and recreate it with the freshly generated secret.
168
177
  if (error?.httpCode == '409') {
169
- Logger.info(
170
- `Webhook ${webhookData.name} already exists in Lime Forms, retrieving existing webhook ID`,
171
- { errorResponse: error.context?.data }
172
- );
173
178
  /**
174
179
  * Error response structure reference:
175
180
  * @see https://github.com/n8n-io/n8n/blob/n8n%401.120.0/packages/core/src/execution-engine/node-execution-context/utils/request-helper-functions.ts#L1280
176
181
  */
177
182
  const errorResponse = error.context?.data?.error;
178
- webhookData.id = errorResponse.id;
179
- webhookData.name = errorResponse.name.replace(
180
- `${FORMS_OBSERVABLE_WEBHOOK_NAME_PREFIX}: `,
181
- ''
183
+ Logger.info(
184
+ `Webhook ${errorResponse?.name} already exists in Lime Forms, recreating it with a fresh secret`,
185
+ { errorResponse: error.context?.data }
186
+ );
187
+
188
+ await new LimeFormsRequest<ObservableWebhookSimpleResource>(
189
+ this
190
+ ).delete(
191
+ `/api/v1/observable-webhooks/${errorResponse.id}`
182
192
  );
193
+
194
+ const response =
195
+ await new LimeFormsRequest<ObservableWebhookDetailedResource>(
196
+ this
197
+ ).post('/api/v1/observable-webhooks', data);
198
+
199
+ if (!response.success) {
200
+ throw new NodeApiError(this.getNode(), {
201
+ message:
202
+ 'Failed to create webhook in Lime Forms',
203
+ });
204
+ }
205
+
206
+ webhookData.id = response.data.id;
183
207
  return true;
184
208
  }
185
209
  throw new NodeApiError(this.getNode(), {
@@ -207,7 +231,7 @@ export class LimeFormsTrigger implements INodeType {
207
231
  }
208
232
 
209
233
  delete webhookData.id;
210
- delete webhookData.secret;
234
+ delete webhookData.webhookSecret;
211
235
 
212
236
  return true;
213
237
  },
@@ -227,9 +251,17 @@ export class LimeFormsTrigger implements INodeType {
227
251
  );
228
252
  }
229
253
 
230
- const webhookSecret = await this.getCredentials(
231
- FORMS_API_CREDENTIALS_NAME
232
- ).then((c) => c.webhookSecret as string);
254
+ const webhookData = this.getWorkflowStaticData('node');
255
+ const encryptedSecret = webhookData.webhookSecret as
256
+ | string
257
+ | undefined;
258
+ if (!encryptedSecret) {
259
+ throw new NodeOperationError(
260
+ this.getNode(),
261
+ 'Webhook is not registered: missing secret in static data'
262
+ );
263
+ }
264
+ const webhookSecret = decryptSecret(encryptedSecret);
233
265
  const isValidSignature = verifyHmac(
234
266
  webhookSecret,
235
267
  Buffer.from(JSON.stringify(body)),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@limetech/n8n-nodes-lime",
3
- "version": "3.10.1",
3
+ "version": "3.10.3",
4
4
  "description": "n8n node to connect to Lime CRM",
5
5
  "author": {
6
6
  "name": "Lime Technologies",