diff --git a/package-lock.json b/package-lock.json index fb069eff5..73fa37e5a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -116,6 +116,10 @@ "resolved": "packages/input_schema", "link": true }, + "node_modules/@apify/input_secrets": { + "resolved": "packages/input_secrets", + "link": true + }, "node_modules/@apify/log": { "resolved": "packages/log", "link": true @@ -3316,6 +3320,17 @@ "integrity": "sha512-K7C7IlQ3zLePEZleUN21ceBA2aLcMnLHTLph8QWk1JK37L90obdpY+QGY8bXMKxf1ht1Z0MNewvXxWv0oGDYFg==", "dev": true }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, "node_modules/@sinonjs/commons": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", @@ -4588,7 +4603,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "engines": { "node": ">=6" } @@ -8284,7 +8298,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true, "engines": { "node": ">=8" } @@ -9795,6 +9808,11 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" + }, "node_modules/lodash.ismatch": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", @@ -11304,6 +11322,38 @@ "node": ">=0.10.0" } }, + "node_modules/ow": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/ow/-/ow-0.28.2.tgz", + "integrity": "sha512-dD4UpyBh/9m4X2NVjA+73/ZPBRF+uF4zIMFvvQsabMiEK8x41L3rQ8EENOi35kyyoaJwNxEeJcP6Fj1H4U409Q==", + "dependencies": { + "@sindresorhus/is": "^4.2.0", + "callsites": "^3.1.0", + "dot-prop": "^6.0.1", + "lodash.isequal": "^4.5.0", + "vali-date": "^1.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ow/node_modules/dot-prop": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", @@ -14210,6 +14260,14 @@ "node": ">=10.12.0" } }, + "node_modules/vali-date": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/vali-date/-/vali-date-1.0.0.tgz", + "integrity": "sha512-sgECfZthyaCKW10N0fm27cg8HYTFK5qMWgypqkXMQ4Wbl/zZKx7xZICgcoxIIE+WFAP/MBL2EFwC/YvLxw3Zeg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -14625,7 +14683,7 @@ }, "packages/consts": { "name": "@apify/consts", - "version": "2.3.0", + "version": "2.4.0", "license": "Apache-2.0" }, "packages/datastructures": { @@ -14712,10 +14770,10 @@ }, "packages/input_schema": { "name": "@apify/input_schema", - "version": "3.1.3", + "version": "3.1.4", "license": "Apache-2.0", "dependencies": { - "@apify/consts": "^2.3.0", + "@apify/consts": "^2.4.0", "countries-list": "^2.6.1", "escaya": "^0.0.61" }, @@ -14723,22 +14781,32 @@ "ajv": "^8.0.0" } }, + "packages/input_secrets": { + "name": "@apify/input_secrets", + "version": "1.0.0", + "license": "Apache-2.0", + "dependencies": { + "@apify/log": "^2.2.0", + "@apify/utilities": "^2.2.2", + "ow": "^0.28.2" + } + }, "packages/log": { "name": "@apify/log", - "version": "2.1.3", + "version": "2.2.0", "license": "Apache-2.0", "dependencies": { - "@apify/consts": "^2.3.0", + "@apify/consts": "^2.4.0", "ansi-colors": "^4.1.1" } }, "packages/markdown": { "name": "@apify/markdown", - "version": "2.0.5", + "version": "2.0.9", "license": "Apache-2.0", "dependencies": { - "@apify/consts": "^2.3.0", - "@apify/utilities": "^2.1.4", + "@apify/consts": "^2.4.0", + "@apify/utilities": "^2.2.2", "marked": "^3.0.2", "match-all": "^1.2.6" } @@ -14756,10 +14824,10 @@ }, "packages/pseudo_url": { "name": "@apify/pseudo_url", - "version": "2.0.4", + "version": "2.0.6", "license": "Apache-2.0", "dependencies": { - "@apify/log": "^2.1.3", + "@apify/log": "^2.2.0", "@sapphire/shapeshift": "^3.6.0" } }, @@ -14779,11 +14847,11 @@ }, "packages/utilities": { "name": "@apify/utilities", - "version": "2.1.4", + "version": "2.2.2", "license": "Apache-2.0", "dependencies": { - "@apify/consts": "^2.3.0", - "@apify/log": "^2.1.3" + "@apify/consts": "^2.4.0", + "@apify/log": "^2.2.0" }, "devDependencies": { "@types/create-hmac": "^1.1.0" @@ -14906,23 +14974,31 @@ "@apify/input_schema": { "version": "file:packages/input_schema", "requires": { - "@apify/consts": "^2.3.0", + "@apify/consts": "^2.4.0", "countries-list": "^2.6.1", "escaya": "^0.0.61" } }, + "@apify/input_secrets": { + "version": "file:packages/input_secrets", + "requires": { + "@apify/log": "^2.2.0", + "@apify/utilities": "^2.2.2", + "ow": "^0.28.2" + } + }, "@apify/log": { "version": "file:packages/log", "requires": { - "@apify/consts": "^2.3.0", + "@apify/consts": "^2.4.0", "ansi-colors": "^4.1.1" } }, "@apify/markdown": { "version": "file:packages/markdown", "requires": { - "@apify/consts": "^2.3.0", - "@apify/utilities": "^2.1.4", + "@apify/consts": "^2.4.0", + "@apify/utilities": "^2.2.2", "marked": "^3.0.2", "match-all": "^1.2.6" } @@ -14937,7 +15013,7 @@ "@apify/pseudo_url": { "version": "file:packages/pseudo_url", "requires": { - "@apify/log": "^2.1.3", + "@apify/log": "^2.2.0", "@sapphire/shapeshift": "^3.6.0" } }, @@ -14954,8 +15030,8 @@ "@apify/utilities": { "version": "file:packages/utilities", "requires": { - "@apify/consts": "^2.3.0", - "@apify/log": "^2.1.3", + "@apify/consts": "^2.4.0", + "@apify/log": "^2.2.0", "@types/create-hmac": "^1.1.0" } }, @@ -17506,6 +17582,11 @@ "integrity": "sha512-K7C7IlQ3zLePEZleUN21ceBA2aLcMnLHTLph8QWk1JK37L90obdpY+QGY8bXMKxf1ht1Z0MNewvXxWv0oGDYFg==", "dev": true }, + "@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==" + }, "@sinonjs/commons": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", @@ -18485,8 +18566,7 @@ "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" }, "camelcase": { "version": "5.3.1", @@ -21176,8 +21256,7 @@ "is-obj": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" }, "is-plain-obj": { "version": "1.1.0", @@ -22316,6 +22395,11 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" + }, "lodash.ismatch": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", @@ -23472,6 +23556,28 @@ "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", "dev": true }, + "ow": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/ow/-/ow-0.28.2.tgz", + "integrity": "sha512-dD4UpyBh/9m4X2NVjA+73/ZPBRF+uF4zIMFvvQsabMiEK8x41L3rQ8EENOi35kyyoaJwNxEeJcP6Fj1H4U409Q==", + "requires": { + "@sindresorhus/is": "^4.2.0", + "callsites": "^3.1.0", + "dot-prop": "^6.0.1", + "lodash.isequal": "^4.5.0", + "vali-date": "^1.0.0" + }, + "dependencies": { + "dot-prop": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "requires": { + "is-obj": "^2.0.0" + } + } + } + }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", @@ -25620,6 +25726,11 @@ "convert-source-map": "^1.6.0" } }, + "vali-date": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/vali-date/-/vali-date-1.0.0.tgz", + "integrity": "sha512-sgECfZthyaCKW10N0fm27cg8HYTFK5qMWgypqkXMQ4Wbl/zZKx7xZICgcoxIIE+WFAP/MBL2EFwC/YvLxw3Zeg==" + }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", diff --git a/packages/input_secrets/CHANGELOG.md b/packages/input_secrets/CHANGELOG.md new file mode 100644 index 000000000..e69de29bb diff --git a/packages/input_secrets/package.json b/packages/input_secrets/package.json new file mode 100644 index 000000000..5a860ec82 --- /dev/null +++ b/packages/input_secrets/package.json @@ -0,0 +1,46 @@ +{ + "name": "@apify/input_secrets", + "version": "1.0.0", + "description": "Package to encrypt and decrypt apify actor input secrets.", + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "typings": "./dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "keywords": [ + "apify" + ], + "author": { + "name": "Apify", + "email": "support@apify.com", + "url": "https://apify.com" + }, + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "git+https://github.com/apify/apify-shared-js" + }, + "bugs": { + "url": "https://github.com/apify/apify-shared-js/issues" + }, + "homepage": "https://apify.com", + "scripts": { + "build": "npm run clean && npm run compile && npm run copy", + "clean": "rimraf ./dist", + "compile": "tsup && tsc -p tsconfig.build.json", + "copy": "ts-node -T ../../scripts/copy.ts" + }, + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@apify/log": "^2.2.0", + "@apify/utilities": "^2.2.2", + "ow": "^0.28.2" + } +} diff --git a/packages/input_secrets/src/index.ts b/packages/input_secrets/src/index.ts new file mode 100644 index 000000000..ed3e6bfd6 --- /dev/null +++ b/packages/input_secrets/src/index.ts @@ -0,0 +1 @@ +export * from './input_secrets'; diff --git a/packages/input_secrets/src/input_secrets.ts b/packages/input_secrets/src/input_secrets.ts new file mode 100644 index 000000000..5e868e0db --- /dev/null +++ b/packages/input_secrets/src/input_secrets.ts @@ -0,0 +1,67 @@ +import ow from 'ow'; +import { KeyObject } from 'crypto'; +import { privateDecrypt, publicEncrypt } from '@apify/utilities'; + +const BASE64_REGEXP = /[-A-Za-z0-9+/]*={0,3}/; +const ENCRYPTED_INPUT_VALUE_PREFIX = 'ENCRYPTED_VALUE'; +const ENCRYPTED_INPUT_VALUE_REGEXP = new RegExp(`^${ENCRYPTED_INPUT_VALUE_PREFIX}:(${BASE64_REGEXP.source}):(${BASE64_REGEXP.source})$`); + +/** + * Get keys of secret fields from input schema + */ +export function getInputSchemaSecretFieldKeys(inputSchema: any): string[] { + return Object.keys(inputSchema.properties) + .filter((key) => !!inputSchema.properties[key].isSecret); +} + +/** + * Encrypts actor input secrets + */ +export function encryptInputSecrets( + { input, inputSchema, publicKey }: { input: T, inputSchema: object, publicKey: KeyObject}, +): T { + ow(input, ow.object); + ow(inputSchema, ow.object); + ow(publicKey, ow.object.instanceOf(KeyObject)); + + const secretsInInputKeys = getInputSchemaSecretFieldKeys(inputSchema); + if (secretsInInputKeys.length === 0) return input; + + const encryptedInput = {}; + for (const key of secretsInInputKeys) { + const value = input[key]; + // NOTE: Skips already encrypted values. It can happens in case client already encrypted values, before + // sending them using API. Or input was takes from task, run console or scheduler, where input is stored encrypted. + if (value && ow.isValid(value, ow.string) && !ENCRYPTED_INPUT_VALUE_REGEXP.test(value)) { + const { encryptedValue, encryptedPassword } = publicEncrypt({ value: input[key], publicKey }); + encryptedInput[key] = `${ENCRYPTED_INPUT_VALUE_PREFIX}:${encryptedPassword}:${encryptedValue}`; + } + } + + return { ...input, ...encryptedInput }; +} + +/** + * Decrypts actor input secrets + * @param {Object} input + * @param {KeyObject} privateKey + * @returns Object + */ +export function decryptInputSecrets( + { input, privateKey }: { input: T, privateKey: KeyObject }, +): T { + ow(input, ow.object); + ow(privateKey, ow.object.instanceOf(KeyObject)); + + const decryptedInput = {}; + for (const [key, value] of Object.entries(input)) { + if (ow.isValid(value, ow.string) && ENCRYPTED_INPUT_VALUE_REGEXP.test(value)) { + const match = value.match(ENCRYPTED_INPUT_VALUE_REGEXP); + if (!match) continue; + const [, encryptedPassword, encryptedValue] = match; + decryptedInput[key] = privateDecrypt({ privateKey, encryptedPassword, encryptedValue }); + } + } + + return { ...input, ...decryptedInput }; +} diff --git a/packages/input_secrets/tsconfig.build.json b/packages/input_secrets/tsconfig.build.json new file mode 100644 index 000000000..3f47ba58f --- /dev/null +++ b/packages/input_secrets/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.build.json", + "compilerOptions": { + "outDir": "./dist", + "emitDeclarationOnly": true + }, + "include": ["src/**/*"] +} diff --git a/packages/input_secrets/tsconfig.json b/packages/input_secrets/tsconfig.json new file mode 100644 index 000000000..52d43eaaa --- /dev/null +++ b/packages/input_secrets/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src/**/*"] +} diff --git a/packages/input_secrets/tsup.config.ts b/packages/input_secrets/tsup.config.ts new file mode 100644 index 000000000..265bee2eb --- /dev/null +++ b/packages/input_secrets/tsup.config.ts @@ -0,0 +1,3 @@ +import { createTsupConfig } from '../../scripts/create-tsup-config'; + +export default createTsupConfig(); diff --git a/test/input_secrets.test.ts b/test/input_secrets.test.ts new file mode 100644 index 000000000..185e686e3 --- /dev/null +++ b/test/input_secrets.test.ts @@ -0,0 +1,51 @@ +import { encryptInputSecrets, decryptInputSecrets } from '@apify/input_secrets'; +import { createPrivateKey, createPublicKey } from 'crypto'; + +const publicKey = createPublicKey({ + // eslint-disable-next-line max-len + key: Buffer.from('LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF0dis3NlNXbklhOFFKWC94RUQxRQpYdnBBQmE3ajBnQnVYenJNUU5adjhtTW1RU0t2VUF0TmpOL2xacUZpQ0haZUQxU2VDcGV1MnFHTm5XbGRxNkhUCnh5cXJpTVZEbFNKaFBNT09QSENISVNVdFI4Tk5lR1Y1MU0wYkxJcENabHcyTU9GUjdqdENWejVqZFRpZ1NvYTIKQWxrRUlRZWQ4UVlDKzk1aGJoOHk5bGcwQ0JxdEdWN1FvMFZQR2xKQ0hGaWNuaWxLVFFZay9MZzkwWVFnUElPbwozbUppeFl5bWFGNmlMZTVXNzg1M0VHWUVFVWdlWmNaZFNjaGVBMEdBMGpRSFVTdnYvMEZjay9adkZNZURJOTVsCmJVQ0JoQjFDbFg4OG4wZUhzUmdWZE5vK0NLMDI4T2IvZTZTK1JLK09VaHlFRVdPTi90alVMdGhJdTJkQWtGcmkKOFFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==', 'base64'), +}); +const privateKey = createPrivateKey({ + // eslint-disable-next-line max-len + key: Buffer.from('LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpQcm9jLVR5cGU6IDQsRU5DUllQVEVECkRFSy1JbmZvOiBERVMtRURFMy1DQkMsNTM1QURERjIzNUQ4QkFGOQoKMXFWUzl0S0FhdkVhVUVFMktESnpjM3plMk1lZkc1dmVEd2o1UVJ0ZkRaMXdWNS9VZmIvcU5sVThTSjlNaGhKaQp6RFdrWExueUUzSW0vcEtITVZkS0czYWZkcFRtcis2TmtidXptd0dVMk0vSWpzRjRJZlpad0lGbGJoY09jUnp4CmZmWVIvTlVyaHNrS1RpNGhGV0lBUDlLb3Z6VDhPSzNZY3h6eVZQWUxYNGVWbWt3UmZzeWkwUU5Xb0tGT3d0ZC8KNm9HYzFnd2piRjI5ZDNnUThZQjFGWmRLa1AyMTJGbkt1cTIrUWgvbE1zTUZrTHlTQTRLTGJ3ZG1RSXExbE1QUwpjbUNtZnppV3J1MlBtNEZoM0dmWlQyaE1JWHlIRFdEVzlDTkxKaERodExOZ2RRamFBUFpVT1E4V2hwSkE5MS9vCjJLZzZ3MDd5Z2RCcVd5dTZrc0pXcjNpZ1JpUEJ5QmVNWEpEZU5HY3NhaUZ3Q2c5eFlja1VORXR3NS90WlRsTjIKSEdZV0NpVU5Ed0F2WllMUHR1SHpIOFRFMGxsZm5HR0VuVC9QQlp1UHV4andlZlRleE1mdzFpbGJRU3lkcy9HMgpOOUlKKzkydms0N0ZXR2NOdGh1Q3lCbklva0NpZ0c1ZlBlV2IwQTdpdjk0UGtwRTRJZ3plc0hGQ0ZFQWoxWldLCnpQdFRBQlkwZlJrUzBNc3UwMHYxOXloTTUrdFUwYkVCZWo2eWpzWHRoYzlwS01hcUNIZWlQTC9TSHRkaWsxNVMKQmU4Sml4dVJxZitUeGlYWWVuNTg2aDlzTFpEYzA3cGpkUGp2NVNYRnBYQjhIMlVxQ0tZY2p4R3RvQWpTV0pjWApMNHc3RHNEby80bVg1N0htR09iamlCN1ZyOGhVWEJDdFh2V0dmQXlmcEFZNS9vOXowdm4zREcxaDc1NVVwdDluCkF2MFZrbm9qcmJVYjM1ZlJuU1lYTVltS01LSnpNRlMrdmFvRlpwV0ZjTG10cFRWSWNzc0JGUEYyZEo3V1c0WHMKK0d2Vkl2eFl3S2wyZzFPTE1TTXRZa09vekdlblBXTzdIdU0yMUVKVGIvbHNEZ25GaTkrYWRGZHBLY3R2cm0zdgpmbW1HeG5pRmhLU05GU0xtNms5YStHL2pjK3NVQVBhb2FZNEQ3NHVGajh0WGp0eThFUHdRRGxVUGRVZld3SE9PClF3bVgyMys1REh4V0VoQy91Tm8yNHNNY2ZkQzFGZUpBV281bUNuVU5vUVVmMStNRDVhMzNJdDhhMmlrNUkxUWoKeSs1WGpRaG0xd3RBMWhWTWE4aUxBR0toT09lcFRuK1VBZHpyS0hvNjVtYzNKbGgvSFJDUXJabnVxWkErK0F2WgpjeWU0dWZGWC8xdmRQSTdLb2Q0MEdDM2dlQnhweFFNYnp1OFNUcGpOcElJRkJvRVc5dFRhemUzeHZXWnV6dDc0CnFjZS8xWURuUHBLeW5lM0xGMk94VWoyYWVYUW5YQkpYcGhTZTBVTGJMcWJtUll4bjJKWkl1d09RNHV5dm94NjUKdG9TWGNac054dUs4QTErZXNXR3JSN3pVc0djdU9QQTFERE9Ja2JjcGtmRUxMNjk4RTJRckdqTU9JWnhrcWdxZQoySE5VNktWRmV2NzdZeEJDbm1VcVdXZEhYMjcyU2NPMUYzdWpUdFVnRVBNWGN0aEdBckYzTWxEaUw1Q0k0RkhqCnhHc3pVemxzalRQTmpiY2MzdUE2MjVZS3VVZEI2c1h1Rk5NUHk5UDgwTzBpRWJGTXl3MWxmN2VpdFhvaUUxWVoKc3NhMDVxTUx4M3pPUXZTLzFDdFpqaFp4cVJMRW5pQ3NWa2JVRlVYclpodEU4dG94bGpWSUtpQ25qbitORmtqdwo2bTZ1anpBSytZZHd2Nk5WMFB4S0gwUk5NYVhwb1lmQk1oUmZ3dGlaS3V3Y2hyRFB5UEhBQ2J3WXNZOXdtUE9rCnpwdDNxWi9JdDVYTmVqNDI0RzAzcGpMbk1sd1B1T1VzYmFQUWQ2VHU4TFhsckZReUVjTXJDNHdjUTA1SzFVN3kKM1NNN3RFaTlnbjV3RjY1YVI5eEFBR0grTUtMMk5WNnQrUmlTazJVaWs1clNmeDE4Mk9wYmpSQ2grdmQ4UXhJdwotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=', 'base64'), + passphrase: 'pwd1234', +}); + +const inputSchema = { + title: 'Test Schema', + type: 'object', + schemaVersion: 1, + properties: { + secure: { + title: 'Secure String', + type: 'string', + editor: 'textfield', + isSecret: true, + description: 'Description', + }, + customString: { + title: 'String', + type: 'string', + editor: 'textfield', + description: 'Description', + }, + }, + required: ['customString'], +}; + +describe('input secrets', () => { + it('should decrypt encrypted values correctly', () => { + const testInput = { secure: 'my secret string', customString: 'just string' }; + const encryptedInput = encryptInputSecrets({ input: testInput, inputSchema, publicKey }); + expect(encryptedInput.secure).not.toEqual(testInput.secure); + expect(encryptedInput.customString).toEqual(testInput.customString); + expect(testInput).toStrictEqual(decryptInputSecrets({ input: encryptedInput, privateKey })); + }); + + it('should not decrypt already decrypted values', () => { + const testInput = { secure: 'my secret', customString: 'just string' }; + const encrypted1 = encryptInputSecrets({ input: testInput, inputSchema, publicKey }); + const encrypted2 = encryptInputSecrets({ input: encrypted1, inputSchema, publicKey }); + expect(testInput).toStrictEqual(decryptInputSecrets({ input: encrypted2, privateKey })); + }); +});