@ixiam/n8n-nodes-civicrm 3.2.2 → 3.2.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.
package/README.md CHANGED
@@ -76,11 +76,14 @@ Time-bounded tokens for enhanced security. **Contact ID is automatically resolve
76
76
 
77
77
  > **Note on warnings vs. results:** When JWT auth is enabled but the token can't be obtained (e.g. AuthX disabled or misconfigured on the CiviCRM side), the node still returns data — it silently retries the same request with the API Key. You'll see a **warning in the node's output pane** explaining why JWT wasn't used, but the workflow does not fail and the query results are still shown. This is intentional: JWT failures never break a workflow, they only fall back to API Key auth and surface a non-blocking warning.
78
78
 
79
+ > **Troubleshooting "JWT authentication could not be obtained (no token returned by CiviCRM)":** This message covers several distinct failures on the CiviCRM side (Contact lookup by API Key failed, or `AuthxCredential.create` was denied), so it doesn't say which one occurred. The most common cause is a missing permission: the Contact that owns the API Key must have the **"AuthX: Generate new JWT credentials for other users via the API"** permission on their role — see [JWT Setup Requirements](#jwt-setup-requirements) above. To confirm which step is failing, call `Contact.get` (filtered by `api_key`) and `AuthxCredential.create` directly against your CiviCRM instance and check the HTTP status/response of each.
80
+
79
81
  #### JWT Setup Requirements
80
82
 
81
83
  **CiviCRM Extensions & Settings:**
82
84
  - ✓ AuthX extension enabled (CiviCRM 5.48+)
83
85
  - ✓ API Key assigned to a Contact record
86
+ - ✓ The Contact's role has the **"AuthX: Generate new JWT credentials for other users via the API"** permission (`generate any authx credential`) — under **Administer → Users and Permissions → Permissions**. Without it, `AuthxCredential.create` returns `403 Authorization failed` and the node silently falls back to API Key auth. Note that having "Administrator"/`administer CiviCRM` does **not** grant this by itself — it must be checked explicitly for the role.
84
87
 
85
88
  **CiviCRM Authentication Configuration**
86
89
 
@@ -703,7 +703,7 @@ class CiviCrm {
703
703
  'for. Leave empty to use the credential (JWT auto-resolve or API key) as before.',
704
704
  displayOptions: {
705
705
  show: {
706
- operation: ['get', 'getMany', 'getFields', 'search', 'raw'],
706
+ operation: ['get', 'getMany', 'getFields', 'search', 'raw', 'create', 'update', 'delete'],
707
707
  },
708
708
  },
709
709
  },
@@ -753,11 +753,16 @@ class CiviCrm {
753
753
  try {
754
754
  // Per-execution JWT for a specific real user (issue #25 - permissions
755
755
  // by real user via Authx), read once per item so it can come from an
756
- // expression like ={{ $json.user_jwt }}. Only threaded into the
757
- // read-only operations this parameter is exposed for (get/getMany/
758
- // getFields/search/raw Custom API Call) - see civicrmApiRequest for
759
- // why an empty result with this token must never fall back to the
760
- // credential's API key.
756
+ // expression like ={{ $json.user_jwt }}. Threaded into every
757
+ // operation this parameter is exposed for - get/getMany/getFields/
758
+ // search/raw Custom API Call, and (issue #30 fix) the fixed-resource
759
+ // create/update/delete branches for Contact/Membership/Group/
760
+ // Relationship/Activity below - see civicrmApiRequest for why an
761
+ // empty result with this token must never fall back to the
762
+ // credential's API key. Without this, a write always ran as the
763
+ // client's admin credential regardless of the real logged-in user's
764
+ // actual CiviCRM permissions, defeating the point of issue #25 for
765
+ // every write operation.
761
766
  const runtimeBearerToken = this.getNodeParameter('runtimeBearerToken', i, '').trim() || undefined;
762
767
  // Custom API resource: raw passthrough, plus first-class List Fields /
763
768
  // Dynamic Search operations for any API4 entity (not just the 5 fixed
@@ -994,7 +999,7 @@ class CiviCrm {
994
999
  const id = this.getNodeParameter('id', i);
995
1000
  const r = await GenericFunctions_1.civicrmApiRequest.call(this, 'POST', `/civicrm/ajax/api4/${entity}/delete`, {
996
1001
  where: [['id', '=', id]],
997
- });
1002
+ }, runtimeBearerToken);
998
1003
  out.push({
999
1004
  json: {
1000
1005
  success: true,
@@ -1130,7 +1135,7 @@ class CiviCrm {
1130
1135
  /* CREATE/UPDATE contact */
1131
1136
  let contactId = id;
1132
1137
  if (isCreate) {
1133
- const r = await GenericFunctions_1.civicrmApiRequest.call(this, 'POST', '/civicrm/ajax/api4/Contact/create', { values });
1138
+ const r = await GenericFunctions_1.civicrmApiRequest.call(this, 'POST', `/civicrm/ajax/api4/${entity}/create`, { values }, runtimeBearerToken);
1134
1139
  contactId = (_k = (_j = r === null || r === void 0 ? void 0 : r.values) === null || _j === void 0 ? void 0 : _j[0]) === null || _k === void 0 ? void 0 : _k.id;
1135
1140
  if (!contactId)
1136
1141
  throw new Error('Failed to create contact.');
@@ -1139,7 +1144,7 @@ class CiviCrm {
1139
1144
  await GenericFunctions_1.civicrmApiRequest.call(this, 'POST', `/civicrm/ajax/api4/${entity}/update`, {
1140
1145
  values,
1141
1146
  where: [['id', '=', contactId]],
1142
- });
1147
+ }, runtimeBearerToken);
1143
1148
  }
1144
1149
  /* SUBENTITIES */
1145
1150
  if (resource === 'contact') {
@@ -1149,7 +1154,7 @@ class CiviCrm {
1149
1154
  ['contact_id', '=', contactId],
1150
1155
  ['is_primary', '=', true],
1151
1156
  ],
1152
- });
1157
+ }, runtimeBearerToken);
1153
1158
  }
1154
1159
  if (isCreate && isPrimaryPhone) {
1155
1160
  await GenericFunctions_1.civicrmApiRequest.call(this, 'POST', '/civicrm/ajax/api4/Phone/delete', {
@@ -1157,7 +1162,7 @@ class CiviCrm {
1157
1162
  ['contact_id', '=', contactId],
1158
1163
  ['is_primary', '=', true],
1159
1164
  ],
1160
- });
1165
+ }, runtimeBearerToken);
1161
1166
  }
1162
1167
  if (isCreate && isPrimaryAddress) {
1163
1168
  await GenericFunctions_1.civicrmApiRequest.call(this, 'POST', '/civicrm/ajax/api4/Address/delete', {
@@ -1165,7 +1170,7 @@ class CiviCrm {
1165
1170
  ['contact_id', '=', contactId],
1166
1171
  ['is_primary', '=', true],
1167
1172
  ],
1168
- });
1173
+ }, runtimeBearerToken);
1169
1174
  }
1170
1175
  if (Object.keys(emailData).length) {
1171
1176
  if (isCreate) {
@@ -1176,7 +1181,7 @@ class CiviCrm {
1176
1181
  is_primary: isPrimaryEmail,
1177
1182
  'location_type_id:name': emailLocationName,
1178
1183
  },
1179
- });
1184
+ }, runtimeBearerToken);
1180
1185
  }
1181
1186
  else {
1182
1187
  const existingEmail = await GenericFunctions_1.civicrmApiRequest.call(this, 'POST', '/civicrm/ajax/api4/Email/get', {
@@ -1186,7 +1191,7 @@ class CiviCrm {
1186
1191
  ],
1187
1192
  limit: 1,
1188
1193
  select: ['id'],
1189
- });
1194
+ }, runtimeBearerToken);
1190
1195
  const existingEmailId = (_m = (_l = existingEmail === null || existingEmail === void 0 ? void 0 : existingEmail.values) === null || _l === void 0 ? void 0 : _l[0]) === null || _m === void 0 ? void 0 : _m.id;
1191
1196
  if (existingEmailId) {
1192
1197
  await GenericFunctions_1.civicrmApiRequest.call(this, 'POST', '/civicrm/ajax/api4/Email/update', {
@@ -1196,7 +1201,7 @@ class CiviCrm {
1196
1201
  contact_id: contactId,
1197
1202
  is_primary: isPrimaryEmail,
1198
1203
  },
1199
- });
1204
+ }, runtimeBearerToken);
1200
1205
  }
1201
1206
  else {
1202
1207
  await GenericFunctions_1.civicrmApiRequest.call(this, 'POST', '/civicrm/ajax/api4/Email/create', {
@@ -1206,7 +1211,7 @@ class CiviCrm {
1206
1211
  is_primary: isPrimaryEmail,
1207
1212
  'location_type_id:name': emailLocationName,
1208
1213
  },
1209
- });
1214
+ }, runtimeBearerToken);
1210
1215
  }
1211
1216
  }
1212
1217
  }
@@ -1219,7 +1224,7 @@ class CiviCrm {
1219
1224
  is_primary: isPrimaryPhone,
1220
1225
  'location_type_id:name': phoneLocationName,
1221
1226
  },
1222
- });
1227
+ }, runtimeBearerToken);
1223
1228
  }
1224
1229
  else {
1225
1230
  const existingPhone = await GenericFunctions_1.civicrmApiRequest.call(this, 'POST', '/civicrm/ajax/api4/Phone/get', {
@@ -1229,7 +1234,7 @@ class CiviCrm {
1229
1234
  ],
1230
1235
  limit: 1,
1231
1236
  select: ['id'],
1232
- });
1237
+ }, runtimeBearerToken);
1233
1238
  const existingPhoneId = (_p = (_o = existingPhone === null || existingPhone === void 0 ? void 0 : existingPhone.values) === null || _o === void 0 ? void 0 : _o[0]) === null || _p === void 0 ? void 0 : _p.id;
1234
1239
  if (existingPhoneId) {
1235
1240
  await GenericFunctions_1.civicrmApiRequest.call(this, 'POST', '/civicrm/ajax/api4/Phone/update', {
@@ -1239,7 +1244,7 @@ class CiviCrm {
1239
1244
  contact_id: contactId,
1240
1245
  is_primary: isPrimaryPhone,
1241
1246
  },
1242
- });
1247
+ }, runtimeBearerToken);
1243
1248
  }
1244
1249
  else {
1245
1250
  await GenericFunctions_1.civicrmApiRequest.call(this, 'POST', '/civicrm/ajax/api4/Phone/create', {
@@ -1249,7 +1254,7 @@ class CiviCrm {
1249
1254
  is_primary: isPrimaryPhone,
1250
1255
  'location_type_id:name': phoneLocationName,
1251
1256
  },
1252
- });
1257
+ }, runtimeBearerToken);
1253
1258
  }
1254
1259
  }
1255
1260
  }
@@ -1262,7 +1267,7 @@ class CiviCrm {
1262
1267
  is_primary: isPrimaryAddress,
1263
1268
  'location_type_id:name': addressLocationName,
1264
1269
  },
1265
- });
1270
+ }, runtimeBearerToken);
1266
1271
  }
1267
1272
  else {
1268
1273
  const existingAddress = await GenericFunctions_1.civicrmApiRequest.call(this, 'POST', '/civicrm/ajax/api4/Address/get', {
@@ -1272,7 +1277,7 @@ class CiviCrm {
1272
1277
  ],
1273
1278
  limit: 1,
1274
1279
  select: ['id'],
1275
- });
1280
+ }, runtimeBearerToken);
1276
1281
  const existingAddressId = (_r = (_q = existingAddress === null || existingAddress === void 0 ? void 0 : existingAddress.values) === null || _q === void 0 ? void 0 : _q[0]) === null || _r === void 0 ? void 0 : _r.id;
1277
1282
  if (existingAddressId) {
1278
1283
  await GenericFunctions_1.civicrmApiRequest.call(this, 'POST', '/civicrm/ajax/api4/Address/update', {
@@ -1282,7 +1287,7 @@ class CiviCrm {
1282
1287
  contact_id: contactId,
1283
1288
  is_primary: isPrimaryAddress,
1284
1289
  },
1285
- });
1290
+ }, runtimeBearerToken);
1286
1291
  }
1287
1292
  else {
1288
1293
  await GenericFunctions_1.civicrmApiRequest.call(this, 'POST', '/civicrm/ajax/api4/Address/create', {
@@ -1292,7 +1297,7 @@ class CiviCrm {
1292
1297
  is_primary: isPrimaryAddress,
1293
1298
  'location_type_id:name': addressLocationName,
1294
1299
  },
1295
- });
1300
+ }, runtimeBearerToken);
1296
1301
  }
1297
1302
  }
1298
1303
  }
@@ -1321,7 +1326,7 @@ class CiviCrm {
1321
1326
  },
1322
1327
  }
1323
1328
  : {}),
1324
- });
1329
+ }, runtimeBearerToken);
1325
1330
  out.push({
1326
1331
  json: (_t = (_s = res === null || res === void 0 ? void 0 : res.values) === null || _s === void 0 ? void 0 : _s[0]) !== null && _t !== void 0 ? _t : {},
1327
1332
  pairedItem: { item: i },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ixiam/n8n-nodes-civicrm",
3
- "version": "3.2.2",
3
+ "version": "3.2.3",
4
4
  "description": "Full-featured CiviCRM API v4 integration for n8n",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -70,4 +70,4 @@
70
70
  }
71
71
  },
72
72
  "packageManager": "pnpm@10.24.0+sha512.01ff8ae71b4419903b65c60fb2dc9d34cf8bb6e06d03bde112ef38f7a34d6904c424ba66bea5cdcf12890230bf39f9580473140ed9c946fef328b6e5238a345a"
73
- }
73
+ }