@tadnt2003/n8n-nodes-infisical 0.3.0 → 0.3.6

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/LICENSE CHANGED
@@ -1,22 +1,22 @@
1
- MIT License
2
-
3
- Copyright (c) 2025 Kennis AI
4
- Copyright (c) 2026 Nguyen Thanh Dat
5
-
6
- Permission is hereby granted, free of charge, to any person obtaining a copy
7
- of this software and associated documentation files (the "Software"), to deal
8
- in the Software without restriction, including without limitation the rights
9
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
- copies of the Software, and to permit persons to whom the Software is
11
- furnished to do so, subject to the following conditions:
12
-
13
- The above copyright notice and this permission notice shall be included in all
14
- copies or substantial portions of the Software.
15
-
16
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Kennis AI
4
+ Copyright (c) 2026 Nguyen Thanh Dat
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
@@ -15,6 +15,7 @@ const CREDENTIAL_FIELD_MAPS = {
15
15
  { param: 'httpNode', secretKey: 'httpNode' },
16
16
  ],
17
17
  googleOAuth2Api: [
18
+ { param: 'serverUrl', secretKey: 'serverUrl' },
18
19
  { param: 'clientId', secretKey: 'clientId' },
19
20
  { param: 'clientSecret', secretKey: 'clientSecret' },
20
21
  { param: 'scope', secretKey: 'scope' },
@@ -210,18 +211,13 @@ const CREDENTIAL_FIELD_MAPS = {
210
211
  { param: 'algorithm', secretKey: 'algorithm' },
211
212
  ],
212
213
  };
214
+ function toSlug(name) {
215
+ return name.trim().replace(/[^a-zA-Z0-9_-]+/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '');
216
+ }
213
217
  function buildSecretPath(rootPath, credentialName) {
214
218
  const root = rootPath.replace(/\/+$/, '') || '/';
215
- return root === '/' ? `/${credentialName}` : `${root}/${credentialName}`;
216
- }
217
- // Fix 7.5: removed `typeof value === 'boolean' && value === false` — false is a meaningful value
218
- // that must be written to Infisical (e.g. ssl:false, sshTunnel:false as condition keys).
219
- function isEmptyValue(value) {
220
- if (value === null || value === undefined)
221
- return true;
222
- if (typeof value === 'string' && value.trim() === '')
223
- return true;
224
- return false;
219
+ const slug = toSlug(credentialName);
220
+ return root === '/' ? `/${slug}` : `${root}/${slug}`;
225
221
  }
226
222
  // Fix 7.4: handles `number` type (port, connectTimeout, maxConnections, etc.)
227
223
  // Fix 7.7: reads schema's own `default` before falling back to enum heuristic
@@ -257,6 +253,23 @@ function coerceValue(raw, def) {
257
253
  return raw === 'true' || raw === '1';
258
254
  return raw;
259
255
  }
256
+ // Decide whether a conditional branch fires given its condition key and values.
257
+ //
258
+ // When condKey IS in schema properties, fire only when the current value matches condValues.
259
+ //
260
+ // When condKey is NOT in schema properties (e.g. useDynamicClientRegistration on
261
+ // googleOAuth2Api), JSON Schema evaluates the if-clause vacuously — the properties keyword
262
+ // has nothing to check, so it passes, and both the [true] branch and the [false] branch
263
+ // appear to fire simultaneously. That is the "vacuous truth" problem.
264
+ //
265
+ // n8n avoids it by internally defaulting the absent key to false before validating, so only
266
+ // the branch whose condValues contain a falsy value actually fires (the "standard mode"
267
+ // branch). We mirror that: condValues=[false] fires, condValues=[true] does not.
268
+ function conditionFires(condKeyInSchema, condValues, condVal) {
269
+ if (condKeyInSchema)
270
+ return condValues.includes(condVal);
271
+ return condValues.some((v) => !v);
272
+ }
260
273
  // Validate credential data against the n8n schema's required fields and conditional requirements.
261
274
  // For form mode, pass availableFormFields to skip checks for fields the form cannot provide.
262
275
  function validateAgainstSchema(data, schemaInfo, availableFormFields) {
@@ -264,20 +277,20 @@ function validateAgainstSchema(data, schemaInfo, availableFormFields) {
264
277
  for (const field of schemaInfo.topRequired) {
265
278
  if (availableFormFields && !availableFormFields.has(field))
266
279
  continue;
267
- if (isEmptyValue(data[field])) {
268
- errors.push(`"${field}" is required but missing or empty`);
280
+ if (data[field] === undefined || data[field] === null) {
281
+ errors.push(`"${field}" is required but not provided`);
269
282
  }
270
283
  }
271
284
  for (const { condKey, condValues, thenRequired } of schemaInfo.condBranches) {
272
285
  const condVal = data[condKey];
273
286
  const condKeyInSchema = condKey in schemaInfo.props;
274
- if (!condKeyInSchema || condValues.includes(condVal)) {
287
+ if (conditionFires(condKeyInSchema, condValues, condVal)) {
275
288
  for (const field of thenRequired) {
276
289
  if (availableFormFields && !availableFormFields.has(field))
277
290
  continue;
278
- if (isEmptyValue(data[field])) {
291
+ if (data[field] === undefined || data[field] === null) {
279
292
  const when = condKeyInSchema ? ` when "${condKey}" is "${String(condVal)}"` : '';
280
- errors.push(`"${field}" is required${when} but missing or empty`);
293
+ errors.push(`"${field}" is required${when} but not provided`);
281
294
  }
282
295
  }
283
296
  }
@@ -430,10 +443,8 @@ function applyCondBranches(fullData, schemaInfo) {
430
443
  var _a, _b;
431
444
  for (const { condKey, condValues, thenRequired, elseProhibited } of schemaInfo.condBranches) {
432
445
  const condVal = fullData[condKey];
433
- // When condKey is absent from schema properties it can't appear in the data,
434
- // so JSON Schema's `properties` validator skips it → condition fires vacuously.
435
446
  const condKeyInSchema = condKey in schemaInfo.props;
436
- if (!condKeyInSchema || condValues.includes(condVal)) {
447
+ if (conditionFires(condKeyInSchema, condValues, condVal)) {
437
448
  // Condition fires → fill any missing then-required fields with safe defaults.
438
449
  for (const field of thenRequired) {
439
450
  if (field in fullData)
@@ -691,7 +702,7 @@ async function executeSyncOperation(ctx, apiUrl, baseHeaders, operation, i) {
691
702
  method: 'POST',
692
703
  url: `${apiUrl}/v2/folders`,
693
704
  headers: baseHeaders,
694
- body: { projectId, environment, name: credentialName, path: folderPath },
705
+ body: { projectId, environment, name: toSlug(credentialName), path: folderPath },
695
706
  });
696
707
  }
697
708
  catch (err) {
@@ -701,12 +712,14 @@ async function executeSyncOperation(ctx, apiUrl, baseHeaders, operation, i) {
701
712
  throw err;
702
713
  }
703
714
  }
704
- // Collect non-empty credential fields as Infisical secrets
715
+ // Collect credential fields as Infisical secrets; empty strings are written as "" so that
716
+ // required-but-blank fields (e.g. serverUrl on googleOAuth2Api) don't fail schema validation
717
+ // on the way back. Only null/undefined is skipped.
705
718
  const secretMetadata = [{ key: 'n8n_credential_type', value: credentialType }];
706
719
  const secrets = [];
707
720
  if (inputMode === 'json') {
708
721
  for (const [key, value] of Object.entries(parsedJson !== null && parsedJson !== void 0 ? parsedJson : {})) {
709
- if (isEmptyValue(value))
722
+ if (value === null || value === undefined)
710
723
  continue;
711
724
  if (fetchedSchemaProps && !(key in fetchedSchemaProps))
712
725
  continue;
@@ -721,7 +734,7 @@ async function executeSyncOperation(ctx, apiUrl, baseHeaders, operation, i) {
721
734
  }
722
735
  for (const { param, secretKey } of fieldMap) {
723
736
  const value = ctx.getNodeParameter(param, i, '');
724
- if (isEmptyValue(value))
737
+ if (value === null || value === undefined)
725
738
  continue;
726
739
  secrets.push({ secretKey, secretValue: String(value), secretMetadata });
727
740
  }
package/package.json CHANGED
@@ -1,57 +1,57 @@
1
- {
2
- "name": "@tadnt2003/n8n-nodes-infisical",
3
- "version": "0.3.0",
4
- "description": "n8n community node for Infisical - Secret management platform",
5
- "keywords": [
6
- "n8n-community-node-package",
7
- "n8n",
8
- "infisical",
9
- "secrets",
10
- "secrets-management"
11
- ],
12
- "license": "MIT",
13
- "homepage": "https://github.com/TadNT2003/n8n-nodes-infisical",
14
- "author": {
15
- "name": "Nguyen Thanh Dat",
16
- "email": "ntdatwows2003@gmail.com",
17
- "url": "https://github.com/TadNT2003"
18
- },
19
- "repository": {
20
- "type": "git",
21
- "url": "git+https://github.com/TadNT2003/n8n-nodes-infisical.git"
22
- },
23
- "scripts": {
24
- "build": "tsc && gulp build",
25
- "dev": "tsc --watch",
26
- "format": "prettier nodes credentials --write",
27
- "lint": "eslint nodes credentials package.json",
28
- "lintfix": "eslint nodes credentials package.json --fix",
29
- "prepublishOnly": "npm run build && npm run lint"
30
- },
31
- "files": [
32
- "dist"
33
- ],
34
- "n8n": {
35
- "n8nNodesApiVersion": 1,
36
- "credentials": [
37
- "dist/credentials/InfisicalApi.credentials.js"
38
- ],
39
- "nodes": [
40
- "dist/nodes/Infisical/Infisical.node.js",
41
- "dist/nodes/InfisicalSync/InfisicalSync.node.js"
42
- ]
43
- },
44
- "devDependencies": {
45
- "@typescript-eslint/eslint-plugin": "^5.62.0",
46
- "@typescript-eslint/parser": "^5.45.0",
47
- "eslint": "^8.29.0",
48
- "eslint-plugin-n8n-nodes-base": "^1.11.0",
49
- "gulp": "^4.0.2",
50
- "n8n-workflow": "^1.120.0",
51
- "prettier": "^2.7.1",
52
- "typescript": "~5.3.0"
53
- },
54
- "peerDependencies": {
55
- "n8n-workflow": "*"
56
- }
57
- }
1
+ {
2
+ "name": "@tadnt2003/n8n-nodes-infisical",
3
+ "version": "0.3.6",
4
+ "description": "n8n community node for Infisical - Secret management platform",
5
+ "keywords": [
6
+ "n8n-community-node-package",
7
+ "n8n",
8
+ "infisical",
9
+ "secrets",
10
+ "secrets-management"
11
+ ],
12
+ "license": "MIT",
13
+ "homepage": "https://github.com/TadNT2003/n8n-nodes-infisical",
14
+ "author": {
15
+ "name": "Nguyen Thanh Dat",
16
+ "email": "ntdatwows2003@gmail.com",
17
+ "url": "https://github.com/TadNT2003"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/TadNT2003/n8n-nodes-infisical.git"
22
+ },
23
+ "scripts": {
24
+ "build": "tsc && gulp build",
25
+ "dev": "tsc --watch",
26
+ "format": "prettier nodes credentials --write",
27
+ "lint": "eslint nodes credentials package.json",
28
+ "lintfix": "eslint nodes credentials package.json --fix",
29
+ "prepublishOnly": "npm run build && npm run lint"
30
+ },
31
+ "files": [
32
+ "dist"
33
+ ],
34
+ "n8n": {
35
+ "n8nNodesApiVersion": 1,
36
+ "credentials": [
37
+ "dist/credentials/InfisicalApi.credentials.js"
38
+ ],
39
+ "nodes": [
40
+ "dist/nodes/Infisical/Infisical.node.js",
41
+ "dist/nodes/InfisicalSync/InfisicalSync.node.js"
42
+ ]
43
+ },
44
+ "devDependencies": {
45
+ "@typescript-eslint/eslint-plugin": "^5.62.0",
46
+ "@typescript-eslint/parser": "^5.45.0",
47
+ "eslint": "^8.29.0",
48
+ "eslint-plugin-n8n-nodes-base": "^1.11.0",
49
+ "gulp": "^4.0.2",
50
+ "n8n-workflow": "^1.120.0",
51
+ "prettier": "^2.7.1",
52
+ "typescript": "~5.3.0"
53
+ },
54
+ "peerDependencies": {
55
+ "n8n-workflow": "*"
56
+ }
57
+ }
@@ -1,5 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
2
- <rect width="24" height="24" rx="4" fill="#000000"/>
3
- <path d="M12 6L6 9v6c0 3.5 2.5 6.5 6 7.5 3.5-1 6-4 6-7.5V9l-6-3z" fill="#FFFFFF"/>
4
- <path d="M12 11v5M10 13l2 2 2-2" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
5
- </svg>
@@ -1,5 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
2
- <rect width="24" height="24" rx="4" fill="#000000"/>
3
- <path d="M12 6L6 9v6c0 3.5 2.5 6.5 6 7.5 3.5-1 6-4 6-7.5V9l-6-3z" fill="#FFFFFF"/>
4
- <path d="M12 11v5M10 13l2 2 2-2" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
5
- </svg>