feat: add node-vault util functions (#6796)

* feat: add `node-vault` util functions

* fix: review comment fixes
This commit is contained in:
lohit
2026-01-13 16:36:40 +00:00
committed by GitHub
parent 36d10ab480
commit 07fff423bb
5 changed files with 1052 additions and 427 deletions

434
package-lock.json generated
View File

@@ -6458,56 +6458,6 @@
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@postman/form-data": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@postman/form-data/-/form-data-3.1.1.tgz",
"integrity": "sha512-vjh8Q2a8S6UCm/KKs31XFJqEEgmbjBmpPNVV2eVav6905wyFAwaUOBGA1NPBI4ERH9MMZc6w0umFgM6WbEPMdg==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/@postman/tough-cookie": {
"version": "4.1.3-postman.1",
"resolved": "https://registry.npmjs.org/@postman/tough-cookie/-/tough-cookie-4.1.3-postman.1.tgz",
"integrity": "sha512-txpgUqZOnWYnUHZpHjkfb0IwVH4qJmyq77pPnJLlfhMtdCLMFTEeQHlzQiK906aaNCe4NEB5fGJHo9uzGbFMeA==",
"license": "BSD-3-Clause",
"dependencies": {
"psl": "^1.1.33",
"punycode": "^2.1.1",
"universalify": "^0.2.0",
"url-parse": "^1.5.3"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@postman/tough-cookie/node_modules/universalify": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
"license": "MIT",
"engines": {
"node": ">= 4.0.0"
}
},
"node_modules/@postman/tunnel-agent": {
"version": "0.6.4",
"resolved": "https://registry.npmjs.org/@postman/tunnel-agent/-/tunnel-agent-0.6.4.tgz",
"integrity": "sha512-CJJlq8V7rNKhAw4sBfjixKpJW00SHqebqNUQKxMoepgeWZIbdPcD+rguRcivGhS4N12PymDcKgUgSD4rVC+RjQ==",
"license": "Apache-2.0",
"dependencies": {
"safe-buffer": "^5.0.1"
},
"engines": {
"node": "*"
}
},
"node_modules/@prantlf/jsonlint": {
"version": "16.0.0",
"resolved": "https://registry.npmjs.org/@prantlf/jsonlint/-/jsonlint-16.0.0.tgz",
@@ -11429,15 +11379,6 @@
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
"license": "MIT"
},
"node_modules/asn1": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
"integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
"license": "MIT",
"dependencies": {
"safer-buffer": "~2.1.0"
}
},
"node_modules/asn1.js": {
"version": "4.10.1",
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz",
@@ -11476,6 +11417,7 @@
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
"integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">=0.8"
}
@@ -11626,15 +11568,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/aws-sign2": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
"integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==",
"license": "Apache-2.0",
"engines": {
"node": "*"
}
},
"node_modules/aws4": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz",
@@ -12028,15 +11961,6 @@
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"license": "MIT"
},
"node_modules/bcrypt-pbkdf": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
"integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==",
"license": "BSD-3-Clause",
"dependencies": {
"tweetnacl": "^0.14.3"
}
},
"node_modules/big.js": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
@@ -12229,15 +12153,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/brotli": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz",
"integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==",
"license": "MIT",
"dependencies": {
"base64-js": "^1.1.2"
}
},
"node_modules/browserify-aes": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
@@ -12750,12 +12665,6 @@
"node": ">=4"
}
},
"node_modules/caseless": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
"integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==",
"license": "Apache-2.0"
},
"node_modules/chai": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz",
@@ -14375,18 +14284,6 @@
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"license": "MIT"
},
"node_modules/dashdash": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
"integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==",
"license": "MIT",
"dependencies": {
"assert-plus": "^1.0.0"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/data-urls": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz",
@@ -15110,22 +15007,6 @@
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"license": "MIT"
},
"node_modules/ecc-jsbn": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
"integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==",
"license": "MIT",
"dependencies": {
"jsbn": "~0.1.0",
"safer-buffer": "^2.1.0"
}
},
"node_modules/ecc-jsbn/node_modules/jsbn": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
"integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==",
"license": "MIT"
},
"node_modules/ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
@@ -16424,12 +16305,6 @@
"node": ">= 0.6"
}
},
"node_modules/extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"license": "MIT"
},
"node_modules/extract-files": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/extract-files/-/extract-files-9.0.0.tgz",
@@ -16555,6 +16430,7 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
"devOptional": true,
"license": "MIT"
},
"node_modules/fast-levenshtein": {
@@ -17037,15 +16913,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/forever-agent": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
"integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==",
"license": "Apache-2.0",
"engines": {
"node": "*"
}
},
"node_modules/fork-ts-checker-webpack-plugin": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-9.1.0.tgz",
@@ -17524,15 +17391,6 @@
"node": ">=6.0"
}
},
"node_modules/getpass": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
"integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==",
"license": "MIT",
"dependencies": {
"assert-plus": "^1.0.0"
}
},
"node_modules/github-markdown-css": {
"version": "5.8.1",
"resolved": "https://registry.npmjs.org/github-markdown-css/-/github-markdown-css-5.8.1.tgz",
@@ -17815,57 +17673,12 @@
"@grpc/grpc-js": "^1.12.6"
}
},
"node_modules/har-schema": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
"integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==",
"license": "ISC",
"engines": {
"node": ">=4"
}
},
"node_modules/har-validator": {
"version": "5.1.5",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz",
"integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==",
"deprecated": "this library is no longer supported",
"license": "MIT",
"dependencies": {
"ajv": "^6.12.3",
"har-schema": "^2.0.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/har-validator-compiled": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/har-validator-compiled/-/har-validator-compiled-1.0.0.tgz",
"integrity": "sha512-dher7nFSx+Ef6OoqVveLClh8itAR3vd8Qx70Lh/hEgP1iGeARAolbci7Y8JBrHIYgFCT6xRdvvL16AR9Zh07Dw==",
"license": "MIT"
},
"node_modules/har-validator/node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/har-validator/node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"license": "MIT"
},
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -18338,20 +18151,6 @@
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/http-signature": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz",
"integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==",
"license": "MIT",
"dependencies": {
"assert-plus": "^1.0.0",
"jsprim": "^2.0.2",
"sshpk": "^1.14.1"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/http2-wrapper": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz",
@@ -19168,12 +18967,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==",
"license": "MIT"
},
"node_modules/is-valid-path": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-valid-path/-/is-valid-path-0.1.1.tgz",
@@ -19236,12 +19029,6 @@
"node": ">=0.10.0"
}
},
"node_modules/isstream": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
"integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==",
"license": "MIT"
},
"node_modules/istanbul-lib-coverage": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
@@ -20822,12 +20609,6 @@
"node": "*"
}
},
"node_modules/json-schema": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
"license": "(AFL-2.1 OR BSD-3-Clause)"
},
"node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
@@ -20851,7 +20632,9 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
"integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
"license": "ISC"
"dev": true,
"license": "ISC",
"optional": true
},
"node_modules/json5": {
"version": "2.2.3",
@@ -20892,44 +20675,6 @@
"node": "*"
}
},
"node_modules/jsprim": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz",
"integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==",
"engines": [
"node >=0.6.0"
],
"license": "MIT",
"dependencies": {
"assert-plus": "1.0.0",
"extsprintf": "1.3.0",
"json-schema": "0.4.0",
"verror": "1.10.0"
}
},
"node_modules/jsprim/node_modules/extsprintf": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
"integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==",
"engines": [
"node >=0.6.0"
],
"license": "MIT"
},
"node_modules/jsprim/node_modules/verror": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
"integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==",
"engines": [
"node >=0.6.0"
],
"license": "MIT",
"dependencies": {
"assert-plus": "^1.0.0",
"core-util-is": "1.0.2",
"extsprintf": "^1.2.0"
}
},
"node_modules/jszip": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
@@ -22030,15 +21775,6 @@
"node": ">= 6.0.0"
}
},
"node_modules/mustache": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz",
"integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==",
"license": "MIT",
"bin": {
"mustache": "bin/mustache"
}
},
"node_modules/mz": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
@@ -22259,44 +21995,6 @@
"integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
"license": "MIT"
},
"node_modules/node-vault": {
"version": "0.10.2",
"resolved": "https://registry.npmjs.org/node-vault/-/node-vault-0.10.2.tgz",
"integrity": "sha512-//uc9/YImE7Dx0QHdwMiAzLaOumiKUnOUP8DymgtkZ8nsq6/V2LKvEu6kw91Lcruw8lWUfj4DO7CIXNPRWBuuA==",
"license": "MIT",
"dependencies": {
"debug": "^4.3.4",
"mustache": "^4.2.0",
"postman-request": "^2.88.1-postman.33",
"tv4": "^1.3.0"
},
"engines": {
"node": ">= 16.0.0"
}
},
"node_modules/node-vault/node_modules/debug": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/node-vault/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@@ -22367,15 +22065,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/oauth-sign": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
"license": "Apache-2.0",
"engines": {
"node": "*"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -23090,12 +22779,6 @@
"integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
"license": "MIT"
},
"node_modules/performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
"license": "MIT"
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -24075,57 +23758,6 @@
"node": ">=15.0.0"
}
},
"node_modules/postman-request": {
"version": "2.88.1-postman.40",
"resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.40.tgz",
"integrity": "sha512-uE4AiIqhjtHKp4pj9ei7fkdfNXEX9IqDBlK1plGAQne6y79UUlrTdtYLhwXoO0AMOvqyl9Ar+BU6Eo6P/MPgfg==",
"license": "Apache-2.0",
"dependencies": {
"@postman/form-data": "~3.1.1",
"@postman/tough-cookie": "~4.1.3-postman.1",
"@postman/tunnel-agent": "^0.6.4",
"aws-sign2": "~0.7.0",
"aws4": "^1.12.0",
"brotli": "^1.3.3",
"caseless": "~0.12.0",
"combined-stream": "~1.0.6",
"extend": "~3.0.2",
"forever-agent": "~0.6.1",
"har-validator": "~5.1.3",
"http-signature": "~1.3.1",
"is-typedarray": "~1.0.0",
"isstream": "~0.1.2",
"json-stringify-safe": "~5.0.1",
"mime-types": "^2.1.35",
"oauth-sign": "~0.9.0",
"performance-now": "^2.1.0",
"qs": "~6.5.3",
"safe-buffer": "^5.1.2",
"stream-length": "^1.0.2",
"uuid": "^8.3.2"
},
"engines": {
"node": ">= 16"
}
},
"node_modules/postman-request/node_modules/qs": {
"version": "6.5.3",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz",
"integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.6"
}
},
"node_modules/postman-request/node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"license": "MIT",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -24427,6 +24059,7 @@
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",
"integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==",
"dev": true,
"license": "MIT",
"dependencies": {
"punycode": "^2.3.1"
@@ -24471,6 +24104,7 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"devOptional": true,
"license": "MIT",
"engines": {
"node": ">=6"
@@ -27386,37 +27020,6 @@
"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
"license": "BSD-3-Clause"
},
"node_modules/sshpk": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz",
"integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==",
"license": "MIT",
"dependencies": {
"asn1": "~0.2.3",
"assert-plus": "^1.0.0",
"bcrypt-pbkdf": "^1.0.0",
"dashdash": "^1.12.0",
"ecc-jsbn": "~0.1.1",
"getpass": "^0.1.1",
"jsbn": "~0.1.0",
"safer-buffer": "^2.0.2",
"tweetnacl": "~0.14.0"
},
"bin": {
"sshpk-conv": "bin/sshpk-conv",
"sshpk-sign": "bin/sshpk-sign",
"sshpk-verify": "bin/sshpk-verify"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/sshpk/node_modules/jsbn": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
"integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==",
"license": "MIT"
},
"node_modules/stable": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz",
@@ -27587,21 +27190,6 @@
"node": ">= 6"
}
},
"node_modules/stream-length": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/stream-length/-/stream-length-1.0.2.tgz",
"integrity": "sha512-aI+qKFiwoDV4rsXiS7WRoCt+v2RX1nUj17+KJC5r2gfh5xoSJIfP6Y3Do/HtvesFcTSWthIuJ3l1cvKQY/+nZg==",
"license": "WTFPL",
"dependencies": {
"bluebird": "^2.6.2"
}
},
"node_modules/stream-length/node_modules/bluebird": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz",
"integrity": "sha512-UfFSr22dmHPQqPP9XWHRhq+gWnHCYguQGkXQlbyPtW5qTnhFWA8/iXg765tH0cAjy7l/zPJ1aBTO0g5XgA7kvQ==",
"license": "MIT"
},
"node_modules/streamsearch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
@@ -29184,12 +28772,6 @@
"node": ">= 0.8.0"
}
},
"node_modules/tweetnacl": {
"version": "0.14.5",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
"integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==",
"license": "Unlicense"
},
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -29454,6 +29036,7 @@
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
"devOptional": true,
"license": "BSD-2-Clause",
"dependencies": {
"punycode": "^2.1.0"
@@ -35492,7 +35075,6 @@
"moment": "^2.29.4",
"nanoid": "3.3.8",
"node-fetch": "^2.7.0",
"node-vault": "^0.10.2",
"path": "^0.12.7",
"quickjs-emscripten": "^0.29.2",
"tv4": "^1.3.0",

View File

@@ -29,7 +29,6 @@
"moment": "^2.29.4",
"nanoid": "3.3.8",
"node-fetch": "^2.7.0",
"node-vault": "^0.10.2",
"path": "^0.12.7",
"quickjs-emscripten": "^0.29.2",
"tv4": "^1.3.0",

View File

@@ -4,5 +4,7 @@ export { WsClient } from './ws/ws-client';
export { default as cookies } from './cookies';
export { getCACertificates } from './utils/ca-cert';
export { default as createVaultClient, VaultError } from './utils/node-vault';
export type { VaultClient, VaultConfig, VaultRequestOptions } from './utils/node-vault';
export * as scripting from './scripting';

View File

@@ -0,0 +1,716 @@
import axios from 'axios';
import createVaultClient, { VaultError, VaultClient } from './node-vault';
// Mock axios
jest.mock('axios', () => {
const mockAxios = jest.fn();
(mockAxios as any).isAxiosError = jest.fn((error: any) => error.isAxiosError === true);
return mockAxios;
});
const mockedAxios = axios as jest.MockedFunction<typeof axios>;
describe('node-vault', () => {
beforeEach(() => {
jest.clearAllMocks();
// Clear environment variables
delete process.env.VAULT_ADDR;
delete process.env.VAULT_TOKEN;
delete process.env.VAULT_NAMESPACE;
});
describe('module', () => {
it('should export a function that returns a new client', () => {
const vault = createVaultClient();
expect(typeof createVaultClient).toBe('function');
expect(typeof vault).toBe('object');
});
it('should set default values for endpoint and apiVersion', () => {
const vault = createVaultClient();
expect(vault.endpoint).toBe('http://127.0.0.1:8200');
expect(vault.apiVersion).toBe('v1');
});
it('should use environment variables for defaults', () => {
process.env.VAULT_ADDR = 'https://vault.example.com';
process.env.VAULT_TOKEN = 'env-token';
process.env.VAULT_NAMESPACE = 'env-namespace';
const vault = createVaultClient();
expect(vault.endpoint).toBe('https://vault.example.com');
expect(vault.token).toBe('env-token');
expect(vault.namespace).toBe('env-namespace');
});
it('should allow config to override environment variables', () => {
process.env.VAULT_ADDR = 'https://vault.example.com';
process.env.VAULT_TOKEN = 'env-token';
const vault = createVaultClient({
endpoint: 'https://custom.vault.com',
token: 'config-token'
});
expect(vault.endpoint).toBe('https://custom.vault.com');
expect(vault.token).toBe('config-token');
});
});
describe('client properties', () => {
it('should allow direct assignment of endpoint', () => {
const vault = createVaultClient();
vault.endpoint = 'https://new-vault.example.com';
expect(vault.endpoint).toBe('https://new-vault.example.com');
});
it('should allow direct assignment of token', () => {
const vault = createVaultClient();
vault.token = 'new-token';
expect(vault.token).toBe('new-token');
});
it('should allow direct assignment of namespace', () => {
const vault = createVaultClient();
vault.namespace = 'my-namespace';
expect(vault.namespace).toBe('my-namespace');
});
it('should allow direct assignment of apiVersion', () => {
const vault = createVaultClient();
vault.apiVersion = 'v2';
expect(vault.apiVersion).toBe('v2');
});
});
describe('read(path, requestOptions)', () => {
let vault: VaultClient;
beforeEach(() => {
vault = createVaultClient({
endpoint: 'http://localhost:8200',
token: 'test-token'
});
});
it('should read data from path', async () => {
const responseData = { data: { value: 'secret-value' } };
mockedAxios.mockResolvedValueOnce({
status: 200,
data: responseData
});
const result = await vault.read('secret/data/hello');
expect(mockedAxios).toHaveBeenCalledTimes(1);
expect(mockedAxios).toHaveBeenCalledWith(
expect.objectContaining({
method: 'GET',
url: 'http://localhost:8200/v1/secret/data/hello',
headers: expect.objectContaining({
'X-Vault-Token': 'test-token'
})
})
);
expect(result).toEqual(responseData);
});
it('should include namespace header when set', async () => {
vault.namespace = 'my-namespace';
mockedAxios.mockResolvedValueOnce({
status: 200,
data: { data: {} }
});
await vault.read('secret/data/hello');
expect(mockedAxios).toHaveBeenCalledWith(
expect.objectContaining({
headers: expect.objectContaining({
'X-Vault-Token': 'test-token',
'X-Vault-Namespace': 'my-namespace'
})
})
);
});
it('should use updated endpoint after assignment', async () => {
vault.endpoint = 'https://new-vault.com';
mockedAxios.mockResolvedValueOnce({
status: 200,
data: { data: {} }
});
await vault.read('secret/data/hello');
expect(mockedAxios).toHaveBeenCalledWith(
expect.objectContaining({
url: 'https://new-vault.com/v1/secret/data/hello'
})
);
});
it('should handle 404 errors', async () => {
mockedAxios.mockResolvedValueOnce({
status: 404,
data: { errors: ['no secrets found'] }
});
await expect(vault.read('secret/data/nonexistent')).rejects.toThrow('no secrets found');
});
it('should handle 204 no content response', async () => {
mockedAxios.mockResolvedValueOnce({
status: 204,
data: null
});
const result = await vault.read('secret/data/empty');
expect(result).toBeNull();
});
it('should handle paths with leading slash without creating double slashes', async () => {
const responseData = { data: { value: 'secret-value' } };
mockedAxios.mockResolvedValueOnce({
status: 200,
data: responseData
});
const result = await vault.read('/secret/data/hello');
expect(mockedAxios).toHaveBeenCalledTimes(1);
expect(mockedAxios).toHaveBeenCalledWith(
expect.objectContaining({
method: 'GET',
url: 'http://localhost:8200/v1/secret/data/hello',
headers: expect.objectContaining({
'X-Vault-Token': 'test-token'
})
})
);
expect(result).toEqual(responseData);
});
it('should handle endpoint with trailing slash', async () => {
vault.endpoint = 'http://localhost:8200/';
const responseData = { data: { value: 'secret-value' } };
mockedAxios.mockResolvedValueOnce({
status: 200,
data: responseData
});
const result = await vault.read('secret/data/hello');
expect(mockedAxios).toHaveBeenCalledTimes(1);
expect(mockedAxios).toHaveBeenCalledWith(
expect.objectContaining({
method: 'GET',
url: 'http://localhost:8200/v1/secret/data/hello'
})
);
expect(result).toEqual(responseData);
});
});
describe('write(path, data, requestOptions)', () => {
let vault: VaultClient;
beforeEach(() => {
vault = createVaultClient({
endpoint: 'http://localhost:8200',
token: 'test-token'
});
});
it('should write data to path', async () => {
const writeData = { value: 'world' };
const responseData = { data: { created_time: '2024-01-01T00:00:00Z' } };
mockedAxios.mockResolvedValueOnce({
status: 200,
data: responseData
});
const result = await vault.write('secret/data/hello', writeData);
expect(mockedAxios).toHaveBeenCalledTimes(1);
expect(mockedAxios).toHaveBeenCalledWith(
expect.objectContaining({
method: 'POST',
url: 'http://localhost:8200/v1/secret/data/hello',
data: writeData,
headers: expect.objectContaining({
'X-Vault-Token': 'test-token',
'Content-Type': 'application/json'
})
})
);
expect(result).toEqual(responseData);
});
it('should handle LDAP login write', async () => {
const loginData = { password: 'my-password' };
const responseData = {
auth: {
client_token: 'ldap-token',
renewable: true,
lease_duration: 3600
}
};
mockedAxios.mockResolvedValueOnce({
status: 200,
data: responseData
});
const result = await vault.write('auth/ldap/login/myuser', loginData);
expect(mockedAxios).toHaveBeenCalledWith(
expect.objectContaining({
method: 'POST',
url: 'http://localhost:8200/v1/auth/ldap/login/myuser',
data: loginData
})
);
expect(result.auth.client_token).toBe('ldap-token');
});
});
describe('approleLogin(args)', () => {
let vault: VaultClient;
beforeEach(() => {
vault = createVaultClient({
endpoint: 'http://localhost:8200'
});
});
it('should login with role_id and secret_id', async () => {
const responseData = {
auth: {
client_token: 'approle-token',
renewable: true,
lease_duration: 3600,
policies: ['default', 'my-policy']
}
};
mockedAxios.mockResolvedValueOnce({
status: 200,
data: responseData
});
const result = await vault.approleLogin({
role_id: 'my-role-id',
secret_id: 'my-secret-id'
});
expect(mockedAxios).toHaveBeenCalledWith(
expect.objectContaining({
method: 'POST',
url: 'http://localhost:8200/v1/auth/approle/login',
data: {
role_id: 'my-role-id',
secret_id: 'my-secret-id'
}
})
);
expect(result.auth.client_token).toBe('approle-token');
});
it('should login with only role_id when secret_id is not required', async () => {
const responseData = {
auth: { client_token: 'approle-token' }
};
mockedAxios.mockResolvedValueOnce({
status: 200,
data: responseData
});
await vault.approleLogin({
role_id: 'my-role-id'
});
expect(mockedAxios).toHaveBeenCalledWith(
expect.objectContaining({
data: {
role_id: 'my-role-id'
}
})
);
});
it('should use custom mount_point', async () => {
mockedAxios.mockResolvedValueOnce({
status: 200,
data: { auth: { client_token: 'token' } }
});
await vault.approleLogin({
role_id: 'my-role-id',
secret_id: 'my-secret-id',
mount_point: 'custom-approle'
});
expect(mockedAxios).toHaveBeenCalledWith(
expect.objectContaining({
url: 'http://localhost:8200/v1/auth/custom-approle/login'
})
);
});
it('should handle authentication errors', async () => {
mockedAxios.mockResolvedValueOnce({
status: 400,
data: { errors: ['invalid role or secret ID'] }
});
await expect(vault.approleLogin({
role_id: 'bad-role-id',
secret_id: 'bad-secret-id'
})).rejects.toThrow('invalid role or secret ID');
});
});
describe('tokenLookupSelf()', () => {
let vault: VaultClient;
beforeEach(() => {
vault = createVaultClient({
endpoint: 'http://localhost:8200',
token: 'my-token'
});
});
it('should lookup current token', async () => {
const responseData = {
data: {
id: 'my-token',
ttl: 3600,
renewable: true,
policies: ['default']
}
};
mockedAxios.mockResolvedValueOnce({
status: 200,
data: responseData
});
const result = await vault.tokenLookupSelf();
expect(mockedAxios).toHaveBeenCalledWith(
expect.objectContaining({
method: 'GET',
url: 'http://localhost:8200/v1/auth/token/lookup-self',
headers: expect.objectContaining({
'X-Vault-Token': 'my-token'
})
})
);
expect(result.data.ttl).toBe(3600);
});
it('should handle expired token error', async () => {
mockedAxios.mockResolvedValueOnce({
status: 403,
data: { errors: ['permission denied'] }
});
await expect(vault.tokenLookupSelf()).rejects.toThrow('permission denied');
});
});
describe('tokenRenewSelf(args)', () => {
let vault: VaultClient;
beforeEach(() => {
vault = createVaultClient({
endpoint: 'http://localhost:8200',
token: 'my-token'
});
});
it('should renew current token', async () => {
const responseData = {
auth: {
client_token: 'my-token',
renewable: true,
lease_duration: 7200
}
};
mockedAxios.mockResolvedValueOnce({
status: 200,
data: responseData
});
const result = await vault.tokenRenewSelf();
expect(mockedAxios).toHaveBeenCalledWith(
expect.objectContaining({
method: 'POST',
url: 'http://localhost:8200/v1/auth/token/renew-self',
headers: expect.objectContaining({
'X-Vault-Token': 'my-token'
})
})
);
expect(result.auth.lease_duration).toBe(7200);
});
it('should pass increment when provided', async () => {
mockedAxios.mockResolvedValueOnce({
status: 200,
data: { auth: { lease_duration: 3600 } }
});
await vault.tokenRenewSelf({ increment: 3600 });
expect(mockedAxios).toHaveBeenCalledWith(
expect.objectContaining({
data: { increment: 3600 }
})
);
});
it('should handle non-renewable token error', async () => {
mockedAxios.mockResolvedValueOnce({
status: 400,
data: { errors: ['lease is not renewable'] }
});
await expect(vault.tokenRenewSelf()).rejects.toThrow('lease is not renewable');
});
});
describe('error handling', () => {
let vault: VaultClient;
beforeEach(() => {
vault = createVaultClient({
endpoint: 'http://localhost:8200',
token: 'test-token'
});
});
it('should throw VaultError with response structure', async () => {
mockedAxios.mockResolvedValueOnce({
status: 500,
data: { errors: ['internal server error'] }
});
try {
await vault.read('secret/data/hello');
} catch (error) {
expect(error).toBeInstanceOf(VaultError);
expect((error as VaultError).message).toBe('internal server error');
expect((error as VaultError).response).toEqual({
statusCode: 500,
status: 500,
body: { errors: ['internal server error'] }
});
}
});
it('should handle error without errors array', async () => {
mockedAxios.mockResolvedValueOnce({
status: 503,
data: {}
});
await expect(vault.read('secret/data/hello')).rejects.toThrow('Status 503');
});
it('should handle network errors', async () => {
const networkError = new Error('Network Error');
(networkError as any).isAxiosError = true;
(networkError as any).code = 'ECONNREFUSED';
mockedAxios.mockRejectedValueOnce(networkError);
try {
await vault.read('secret/data/hello');
} catch (error) {
expect((error as any).message).toBe('Network Error');
expect((error as any).code).toBe('ECONNREFUSED');
}
});
it('should handle axios error with response', async () => {
const axiosError = new Error('Request failed');
(axiosError as any).isAxiosError = true;
(axiosError as any).response = {
status: 401,
data: { errors: ['permission denied'] }
};
mockedAxios.mockRejectedValueOnce(axiosError);
await expect(vault.read('secret/data/hello')).rejects.toThrow('permission denied');
});
it('should pass through non-axios errors', async () => {
const genericError = new Error('Unknown error');
mockedAxios.mockRejectedValueOnce(genericError);
await expect(vault.read('secret/data/hello')).rejects.toThrow('Unknown error');
});
});
describe('requestOptions', () => {
it('should pass strictSSL to https agent', async () => {
const vault = createVaultClient({
endpoint: 'https://vault.example.com',
token: 'test-token',
requestOptions: {
strictSSL: false
}
});
mockedAxios.mockResolvedValueOnce({
status: 200,
data: {}
});
await vault.read('secret/data/hello');
expect(mockedAxios).toHaveBeenCalledWith(
expect.objectContaining({
httpsAgent: expect.any(Object)
})
);
});
it('should not set httpsAgent for http endpoints', async () => {
const vault = createVaultClient({
endpoint: 'http://localhost:8200',
token: 'test-token',
requestOptions: {
strictSSL: false
}
});
mockedAxios.mockResolvedValueOnce({
status: 200,
data: {}
});
await vault.read('secret/data/hello');
const callArgs = mockedAxios.mock.calls[0][0] as any;
expect(callArgs.httpsAgent).toBeUndefined();
});
it('should configure proxy when provided', async () => {
const vault = createVaultClient({
endpoint: 'http://localhost:8200',
token: 'test-token',
requestOptions: {
proxy: 'http://proxy.example.com:8080'
}
});
mockedAxios.mockResolvedValueOnce({
status: 200,
data: {}
});
await vault.read('secret/data/hello');
expect(mockedAxios).toHaveBeenCalledWith(
expect.objectContaining({
proxy: expect.objectContaining({
host: 'proxy.example.com',
port: 8080
})
})
);
});
it('should configure proxy with authentication', async () => {
const vault = createVaultClient({
endpoint: 'http://localhost:8200',
token: 'test-token',
requestOptions: {
proxy: 'http://user:pass@proxy.example.com:8080'
}
});
mockedAxios.mockResolvedValueOnce({
status: 200,
data: {}
});
await vault.read('secret/data/hello');
expect(mockedAxios).toHaveBeenCalledWith(
expect.objectContaining({
proxy: expect.objectContaining({
host: 'proxy.example.com',
port: 8080,
auth: {
username: 'user',
password: 'pass'
}
})
})
);
});
});
describe('URL construction', () => {
it('should construct URL with apiVersion', async () => {
const vault = createVaultClient({
endpoint: 'http://localhost:8200',
apiVersion: 'v2'
});
mockedAxios.mockResolvedValueOnce({
status: 200,
data: {}
});
await vault.read('secret/data/hello');
expect(mockedAxios).toHaveBeenCalledWith(
expect.objectContaining({
url: 'http://localhost:8200/v2/secret/data/hello'
})
);
});
it('should handle endpoint without trailing slash', async () => {
const vault = createVaultClient({
endpoint: 'http://localhost:8200'
});
mockedAxios.mockResolvedValueOnce({
status: 200,
data: {}
});
await vault.read('secret/data/hello');
expect(mockedAxios).toHaveBeenCalledWith(
expect.objectContaining({
url: 'http://localhost:8200/v1/secret/data/hello'
})
);
});
});
describe('health endpoint handling', () => {
it('should not throw error for sys/health even with non-200 status', async () => {
const vault = createVaultClient({
endpoint: 'http://localhost:8200'
});
const healthResponse = {
initialized: true,
sealed: true,
standby: true
};
mockedAxios.mockResolvedValueOnce({
status: 503,
data: healthResponse
});
const result = await vault.read('sys/health');
expect(result).toEqual(healthResponse);
});
});
});

View File

@@ -0,0 +1,326 @@
import axios, { AxiosRequestConfig, AxiosError } from 'axios';
import * as https from 'node:https';
/**
* Configuration options for creating a Vault client
*/
export interface VaultConfig {
apiVersion?: string;
endpoint?: string;
token?: string;
namespace?: string;
requestOptions?: VaultRequestOptions;
debug?: (...args: any[]) => void;
}
/**
* Request options for Vault HTTP requests
* Compatible with node-vault's requestOptions
*/
export interface VaultRequestOptions {
strictSSL?: boolean;
ca?: string | Buffer | Array<string | Buffer>;
proxy?: string;
[key: string]: any;
}
/**
* AppRole login arguments
*/
export interface ApproleLoginArgs {
role?: string;
role_id: string;
secret_id?: string;
mount_point?: string;
}
/**
* Token renew arguments
*/
export interface TokenRenewArgs {
increment?: number | string;
}
/**
* Vault API response error structure
* Includes both statusCode (node-vault style) and status (axios style) for compatibility
*/
export class VaultError extends Error {
response?: {
statusCode: number;
status: number; // Alias for axios-style error handling
body: any;
};
code?: string; // For network errors
constructor(message: string, response?: { statusCode: number; body: any }) {
super(message);
this.name = 'VaultError';
if (response) {
this.response = {
statusCode: response.statusCode,
status: response.statusCode, // Alias for compatibility
body: response.body
};
}
}
}
/**
* Vault client interface - matches node-vault API surface
*/
export interface VaultClient {
endpoint: string;
namespace?: string;
token?: string;
apiVersion: string;
read(path: string, requestOptions?: VaultRequestOptions): Promise<any>;
write(path: string, data: any, requestOptions?: VaultRequestOptions): Promise<any>;
approleLogin(args: ApproleLoginArgs): Promise<any>;
tokenLookupSelf(args?: any): Promise<any>;
tokenRenewSelf(args?: TokenRenewArgs): Promise<any>;
}
/**
* Creates an HTTPS agent based on request options
*/
function createHttpsAgent(options: VaultRequestOptions): https.Agent | undefined {
const agentOptions: https.AgentOptions = {};
let needsAgent = false;
if (options.strictSSL === false) {
agentOptions.rejectUnauthorized = false;
needsAgent = true;
}
if (options.ca) {
agentOptions.ca = options.ca;
needsAgent = true;
}
return needsAgent ? new https.Agent(agentOptions) : undefined;
}
/**
* Handles Vault API response, extracting body or throwing error
*/
function handleVaultResponse(statusCode: number, body: any, path: string): any {
// Success responses
if (statusCode === 200 || statusCode === 204) {
return body;
}
// Health endpoint special handling (matches node-vault behavior)
if (path.match(/sys\/health/) !== null) {
return body;
}
// Error responses
let message: string;
if (body && body.errors && body.errors.length > 0) {
message = body.errors[0];
} else {
message = `Status ${statusCode}`;
}
throw new VaultError(message, { statusCode, body });
}
/**
* Creates a Vault client instance
*
* This is a drop-in replacement for node-vault, implementing only the methods
* used by bruno-electron and bruno-cli.
*
* @param config - Configuration options
* @returns VaultClient instance with mutable properties
*
* @example
* ```javascript
* const vault = createVaultClient({ apiVersion: 'v1' });
* vault.endpoint = 'https://vault.example.com';
* vault.token = 'my-token';
* const secret = await vault.read('secret/data/myapp');
* ```
*/
function createVaultClient(config: VaultConfig = {}): VaultClient {
const debug = config.debug || (() => {});
const defaultRequestOptions = config.requestOptions || {};
/**
* Makes an HTTP request to the Vault API
*/
async function request(
method: string,
path: string,
data?: any,
requestOptions?: VaultRequestOptions
): Promise<any> {
// Merge request options: defaults from config + per-request options
const mergedOptions: VaultRequestOptions = {
...defaultRequestOptions,
...requestOptions
};
const endpointOrigin = client.endpoint?.endsWith('/') ? client.endpoint : `${client.endpoint}/`;
// Build URL
const uri = `${endpointOrigin}${client.apiVersion}${path}`;
debug(method, uri);
// Build headers
const headers: Record<string, string> = {
'Content-Type': 'application/json'
};
if (typeof client.token === 'string' && client.token.length) {
headers['X-Vault-Token'] = client.token;
}
if (typeof client.namespace === 'string' && client.namespace.length) {
headers['X-Vault-Namespace'] = client.namespace;
}
// Build axios config
const axiosConfig: AxiosRequestConfig = {
method: method as any,
url: uri,
headers,
validateStatus: () => true // Don't throw on non-2xx status
};
// Add request body for POST/PUT
if (data && (method === 'POST' || method === 'PUT')) {
axiosConfig.data = data;
debug('data:', data);
}
// Configure HTTPS agent
if (uri.startsWith('https')) {
const agent = createHttpsAgent(mergedOptions);
if (agent) {
axiosConfig.httpsAgent = agent;
}
}
// Configure proxy
if (mergedOptions.proxy) {
// Parse proxy URL into axios proxy config
try {
const proxyUrl = new URL(mergedOptions.proxy);
axiosConfig.proxy = {
host: proxyUrl.hostname,
port: parseInt(proxyUrl.port, 10) || (proxyUrl.protocol === 'https:' ? 443 : 80),
protocol: proxyUrl.protocol.replace(':', '')
};
if (proxyUrl.username && proxyUrl.password) {
axiosConfig.proxy.auth = {
username: decodeURIComponent(proxyUrl.username),
password: decodeURIComponent(proxyUrl.password)
};
}
} catch (e) {
// If proxy URL parsing fails, pass it as-is for backward compatibility
debug('Failed to parse proxy URL:', mergedOptions.proxy);
}
}
try {
const response = await axios(axiosConfig);
return handleVaultResponse(response.status, response.data, path);
} catch (error) {
// Network errors or other axios errors
if (axios.isAxiosError(error)) {
const axiosError = error as AxiosError;
if (axiosError.response) {
// Server responded with error status
return handleVaultResponse(
axiosError.response.status,
axiosError.response.data,
path
);
}
// Network error - preserve original error structure
const vaultError = new VaultError(axiosError.message);
(vaultError as any).code = axiosError.code;
throw vaultError;
}
throw error;
}
}
// Create client object with mutable properties
const client: VaultClient = {
// Mutable properties (support direct assignment like node-vault)
apiVersion: config.apiVersion || 'v1',
endpoint: config.endpoint || process.env.VAULT_ADDR || 'http://127.0.0.1:8200',
token: config.token || process.env.VAULT_TOKEN,
namespace: config.namespace || process.env.VAULT_NAMESPACE,
/**
* Read data from a Vault path
* @param path - The path to read from (e.g., 'secret/data/myapp')
* @param requestOptions - Optional request options
*/
async read(path: string, requestOptions?: VaultRequestOptions): Promise<any> {
path = path.startsWith('/') ? path : `/${path}`;
debug('read', path);
return request('GET', path, undefined, requestOptions);
},
/**
* Write data to a Vault path
* @param path - The path to write to
* @param data - The data to write
* @param requestOptions - Optional request options
*/
async write(path: string, data: any, requestOptions?: VaultRequestOptions): Promise<any> {
path = path.startsWith('/') ? path : `/${path}`;
debug('write', path, data);
return request('POST', path, data, requestOptions);
},
/**
* Authenticate using AppRole
* @param args - AppRole login arguments
*/
async approleLogin(args: ApproleLoginArgs): Promise<any> {
debug('approleLogin', args.role_id);
const mountPoint = args.mount_point || 'approle';
const body: Record<string, any> = {
role_id: args.role_id
};
if (args.secret_id) {
body.secret_id = args.secret_id;
}
return request('POST', `/auth/${mountPoint}/login`, body);
},
/**
* Look up the current token's properties
*/
async tokenLookupSelf(args?: any): Promise<any> {
debug('tokenLookupSelf');
return request('GET', '/auth/token/lookup-self');
},
/**
* Renew the current token
* @param args - Optional arguments including increment
*/
async tokenRenewSelf(args?: TokenRenewArgs): Promise<any> {
debug('tokenRenewSelf');
const body: Record<string, any> = {};
if (args?.increment !== undefined) {
body.increment = args.increment;
}
return request('POST', '/auth/token/renew-self', Object.keys(body).length > 0 ? body : undefined);
}
};
return client;
}
export default createVaultClient;