diff --git a/apps/desktop/config/development.json b/apps/desktop/config/development.json index 3c93018e65f..b587e9ecfb9 100644 --- a/apps/desktop/config/development.json +++ b/apps/desktop/config/development.json @@ -1,4 +1,6 @@ { "devFlags": {}, - "flags": {} + "flags": { + "showDDGSetting": true + } } diff --git a/apps/desktop/config/production.json b/apps/desktop/config/production.json index b04d1531a2f..56f19341304 100644 --- a/apps/desktop/config/production.json +++ b/apps/desktop/config/production.json @@ -1,3 +1,5 @@ { - "flags": {} + "flags": { + "showDDGSetting": true + } } diff --git a/apps/desktop/native-messaging-test-runner/.eslintrc.json b/apps/desktop/native-messaging-test-runner/.eslintrc.json new file mode 100644 index 00000000000..d5ba8f9d9ca --- /dev/null +++ b/apps/desktop/native-messaging-test-runner/.eslintrc.json @@ -0,0 +1,5 @@ +{ + "rules": { + "no-console": "off" + } +} diff --git a/apps/desktop/native-messaging-test-runner/package-lock.json b/apps/desktop/native-messaging-test-runner/package-lock.json new file mode 100644 index 00000000000..34b1bb54068 --- /dev/null +++ b/apps/desktop/native-messaging-test-runner/package-lock.json @@ -0,0 +1,681 @@ +{ + "name": "native-messaging-test-runner", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "native-messaging-test-runner", + "version": "1.0.0", + "license": "GPL-3.0", + "dependencies": { + "@bitwarden/common": "file:../../../libs/common", + "@bitwarden/node": "file:../../../libs/node", + "module-alias": "^2.2.2", + "node-ipc": "9.2.1", + "ts-node": "^10.9.1", + "uuid": "^8.3.2", + "yargs": "^17.5.1" + }, + "devDependencies": { + "@tsconfig/node16": "^1.0.3", + "@types/node": "^18.6.5", + "@types/node-ipc": "9.2.0", + "typescript": "^4.7.4" + } + }, + "../../../libs/common": { + "name": "@bitwarden/common", + "version": "0.0.0", + "license": "GPL-3.0" + }, + "../../../libs/node": { + "name": "@bitwarden/node", + "version": "0.0.0", + "license": "GPL-3.0", + "dependencies": { + "@bitwarden/common": "file:../common" + } + }, + "node_modules/@bitwarden/common": { + "resolved": "../../../libs/common", + "link": true + }, + "node_modules/@bitwarden/node": { + "resolved": "../../../libs/node", + "link": true + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.3", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "18.6.5", + "license": "MIT" + }, + "node_modules/@types/node-ipc": { + "version": "9.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/acorn": { + "version": "8.8.0", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "license": "MIT" + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/create-require": { + "version": "1.1.1", + "license": "MIT" + }, + "node_modules/diff": { + "version": "4.0.2", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/easy-stack": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/easy-stack/-/easy-stack-1.0.1.tgz", + "integrity": "sha512-wK2sCs4feiiJeFXn3zvY0p41mdU5VUgbgs1rNsc/y5ngFUijdWd+iIN8eoyuZHKB8xN6BL4PdWmzqFmxNg6V2w==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/event-pubsub": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/event-pubsub/-/event-pubsub-4.3.0.tgz", + "integrity": "sha512-z7IyloorXvKbFx9Bpie2+vMJKKx1fH1EN5yiTfp8CiLOTptSYy1g8H4yDpGlEdshL1PBiFtBHepF2cNsqeEeFQ==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/js-message": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/js-message/-/js-message-1.0.7.tgz", + "integrity": "sha512-efJLHhLjIyKRewNS9EGZ4UpI8NguuL6fKkhRxVuMmrGV2xN/0APGdQYwLFky5w9naebSZ0OwAGp0G6/2Cg90rA==", + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/js-queue": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/js-queue/-/js-queue-2.0.2.tgz", + "integrity": "sha512-pbKLsbCfi7kriM3s1J4DDCo7jQkI58zPLHi0heXPzPlj0hjUsm+FesPUbE0DSbIVIK503A36aUBoCN7eMFedkA==", + "dependencies": { + "easy-stack": "^1.0.1" + }, + "engines": { + "node": ">=1.0.0" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "license": "ISC" + }, + "node_modules/module-alias": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.2.tgz", + "integrity": "sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==" + }, + "node_modules/node-ipc": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/node-ipc/-/node-ipc-9.2.1.tgz", + "integrity": "sha512-mJzaM6O3xHf9VT8BULvJSbdVbmHUKRNOH7zDDkCrA1/T+CVjq2WVIDfLt0azZRXpgArJtl3rtmEozrbXPZ9GaQ==", + "dependencies": { + "event-pubsub": "4.3.0", + "js-message": "1.0.7", + "js-queue": "2.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ts-node": { + "version": "10.9.1", + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/typescript": { + "version": "4.7.4", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "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==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", + "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + } + }, + "dependencies": { + "@bitwarden/common": { + "version": "file:../../../libs/common" + }, + "@bitwarden/node": { + "version": "file:../../../libs/node", + "requires": { + "@bitwarden/common": "file:../common" + } + }, + "@cspotcode/source-map-support": { + "version": "0.8.1", + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0" + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14" + }, + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@tsconfig/node10": { + "version": "1.0.9" + }, + "@tsconfig/node12": { + "version": "1.0.11" + }, + "@tsconfig/node14": { + "version": "1.0.3" + }, + "@tsconfig/node16": { + "version": "1.0.3" + }, + "@types/node": { + "version": "18.6.5" + }, + "@types/node-ipc": { + "version": "9.2.0", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "acorn": { + "version": "8.8.0" + }, + "acorn-walk": { + "version": "8.2.0" + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "arg": { + "version": "4.1.3" + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "create-require": { + "version": "1.1.1" + }, + "diff": { + "version": "4.0.2" + }, + "easy-stack": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/easy-stack/-/easy-stack-1.0.1.tgz", + "integrity": "sha512-wK2sCs4feiiJeFXn3zvY0p41mdU5VUgbgs1rNsc/y5ngFUijdWd+iIN8eoyuZHKB8xN6BL4PdWmzqFmxNg6V2w==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" + }, + "event-pubsub": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/event-pubsub/-/event-pubsub-4.3.0.tgz", + "integrity": "sha512-z7IyloorXvKbFx9Bpie2+vMJKKx1fH1EN5yiTfp8CiLOTptSYy1g8H4yDpGlEdshL1PBiFtBHepF2cNsqeEeFQ==" + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "js-message": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/js-message/-/js-message-1.0.7.tgz", + "integrity": "sha512-efJLHhLjIyKRewNS9EGZ4UpI8NguuL6fKkhRxVuMmrGV2xN/0APGdQYwLFky5w9naebSZ0OwAGp0G6/2Cg90rA==", + "resolved": "https://registry.npmjs.org/js-message/-/js-message-1.0.7.tgz", + "integrity": "sha512-efJLHhLjIyKRewNS9EGZ4UpI8NguuL6fKkhRxVuMmrGV2xN/0APGdQYwLFky5w9naebSZ0OwAGp0G6/2Cg90rA==" + }, + "js-queue": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/js-queue/-/js-queue-2.0.2.tgz", + "integrity": "sha512-pbKLsbCfi7kriM3s1J4DDCo7jQkI58zPLHi0heXPzPlj0hjUsm+FesPUbE0DSbIVIK503A36aUBoCN7eMFedkA==", + "resolved": "https://registry.npmjs.org/js-queue/-/js-queue-2.0.2.tgz", + "integrity": "sha512-pbKLsbCfi7kriM3s1J4DDCo7jQkI58zPLHi0heXPzPlj0hjUsm+FesPUbE0DSbIVIK503A36aUBoCN7eMFedkA==", + "requires": { + "easy-stack": "^1.0.1" + } + }, + "make-error": { + "version": "1.3.6" + }, + "module-alias": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.2.tgz", + "integrity": "sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==" + }, + "node-ipc": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/node-ipc/-/node-ipc-9.2.1.tgz", + "integrity": "sha512-mJzaM6O3xHf9VT8BULvJSbdVbmHUKRNOH7zDDkCrA1/T+CVjq2WVIDfLt0azZRXpgArJtl3rtmEozrbXPZ9GaQ==", + "requires": { + "event-pubsub": "4.3.0", + "js-message": "1.0.7", + "js-queue": "2.0.2" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "ts-node": { + "version": "10.9.1", + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + } + }, + "typescript": { + "version": "4.7.4" + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + }, + "v8-compile-cache-lib": { + "version": "3.0.1" + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" + }, + "yargs": { + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", + "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" + }, + "yn": { + "version": "3.1.1" + } + } +} diff --git a/apps/desktop/native-messaging-test-runner/package.json b/apps/desktop/native-messaging-test-runner/package.json new file mode 100644 index 00000000000..85d4a1ab6a6 --- /dev/null +++ b/apps/desktop/native-messaging-test-runner/package.json @@ -0,0 +1,35 @@ +{ + "name": "native-messaging-test-runner", + "version": "1.0.0", + "description": "Test runner for Desktop native messaging", + "main": "dist/bw-handshake.ts", + "scripts": { + "handshake": "tsc && node dist/apps/desktop/native-messaging-test-runner/src/commands/bw-handshake.js", + "status": "tsc && node dist/apps/desktop/native-messaging-test-runner/src/commands/bw-status.js", + "retrieve": "tsc && node dist/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-retrieval.js", + "create": "tsc && node dist/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-create.js", + "update": "tsc && node dist/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-update.js", + "generate": "tsc && node dist/apps/desktop/native-messaging-test-runner/src/commands/bw-generate-password.js" + }, + "author": "Bitwarden Inc. (https://bitwarden.com)", + "license": "GPL-3.0", + "dependencies": { + "@bitwarden/common": "file:../../../libs/common", + "@bitwarden/node": "file:../../../libs/node", + "module-alias": "^2.2.2", + "node-ipc": "9.2.1", + "ts-node": "^10.9.1", + "uuid": "^8.3.2", + "yargs": "^17.5.1" + }, + "devDependencies": { + "@tsconfig/node16": "^1.0.3", + "@types/node": "^18.6.5", + "@types/node-ipc": "9.2.0", + "typescript": "^4.7.4" + }, + "_moduleAliases": { + "@bitwarden/common": "dist/libs/common/src", + "@bitwarden/node/services/nodeCryptoFunction.service": "dist/libs/node/src/services/nodeCryptoFunction.service" + } +} diff --git a/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-create.ts b/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-create.ts new file mode 100644 index 00000000000..a017b4c72cb --- /dev/null +++ b/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-create.ts @@ -0,0 +1,66 @@ +import "module-alias/register"; + +import yargs from "yargs"; +import { hideBin } from "yargs/helpers"; + +import { NativeMessagingVersion } from "@bitwarden/common/enums/nativeMessagingVersion"; + +import { CredentialCreatePayload } from "../../../src/models/nativeMessaging/encryptedMessagePayloads/credentialCreatePayload"; +import { LogUtils } from "../logUtils"; +import NativeMessageService from "../nativeMessageService"; +import * as config from "../variables"; + +const argv: any = yargs(hideBin(process.argv)).option("name", { + alias: "n", + demand: true, + describe: "Name that the created login will be given", + type: "string", +}).argv; + +const { name } = argv; + +(async () => { + const nativeMessageService = new NativeMessageService(NativeMessagingVersion.One); + // Handshake + LogUtils.logInfo("Sending Handshake"); + const handshakeResponse = await nativeMessageService.sendHandshake( + config.testRsaPublicKey, + config.applicationName + ); + + if (!handshakeResponse.status) { + LogUtils.logError(" Handshake failed. Error was: " + handshakeResponse.error); + nativeMessageService.disconnect(); + return; + } + + // Get active account userId + const status = await nativeMessageService.checkStatus(handshakeResponse.sharedKey); + + const activeUser = status.payload.filter((a) => a.active === true && a.status === "unlocked")[0]; + if (activeUser === undefined) { + LogUtils.logError("No active or unlocked user"); + } + LogUtils.logInfo("Active userId: " + activeUser.id); + + LogUtils.logSuccess("Handshake success response"); + const response = await nativeMessageService.credentialCreation(handshakeResponse.sharedKey, { + name: name, + userName: "SuperAwesomeUser", + password: "dolhpin", + uri: "google.com", + userId: activeUser.id, + } as CredentialCreatePayload); + + if (response.payload.status === "failure") { + LogUtils.logError("Failure response returned "); + } else if (response.payload.status === "success") { + LogUtils.logSuccess("Success response returned "); + } else if (response.payload.error === "locked") { + LogUtils.logError("Error: vault is locked"); + } else { + LogUtils.logWarning("Other response: ", response); + } + + nativeMessageService.disconnect(); +})(); diff --git a/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-retrieval.ts b/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-retrieval.ts new file mode 100644 index 00000000000..17244623b55 --- /dev/null +++ b/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-retrieval.ts @@ -0,0 +1,46 @@ +import "module-alias/register"; + +import yargs from "yargs"; +import { hideBin } from "yargs/helpers"; + +import { NativeMessagingVersion } from "@bitwarden/common/enums/nativeMessagingVersion"; + +import { LogUtils } from "../logUtils"; +import NativeMessageService from "../nativeMessageService"; +import * as config from "../variables"; + +const argv: any = yargs(hideBin(process.argv)).option("uri", { + alias: "u", + demand: true, + describe: "The uri to retrieve logins for", + type: "string", +}).argv; + +const { uri } = argv; + +(async () => { + const nativeMessageService = new NativeMessageService(NativeMessagingVersion.One); + // Handshake + LogUtils.logInfo("Sending Handshake"); + const handshakeResponse = await nativeMessageService.sendHandshake( + config.testRsaPublicKey, + config.applicationName + ); + + if (!handshakeResponse.status) { + LogUtils.logError(" Handshake failed. Error was: " + handshakeResponse.error); + nativeMessageService.disconnect(); + return; + } + + LogUtils.logSuccess("Handshake success response"); + const response = await nativeMessageService.credentialRetrieval(handshakeResponse.sharedKey, uri); + + if (response.payload.error != null) { + LogUtils.logError("Error response returned: ", response.payload.error); + } else { + LogUtils.logSuccess("Credentials returned ", response); + } + + nativeMessageService.disconnect(); +})(); diff --git a/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-update.ts b/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-update.ts new file mode 100644 index 00000000000..ecfbd3f5bb8 --- /dev/null +++ b/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-update.ts @@ -0,0 +1,89 @@ +import "module-alias/register"; + +import yargs from "yargs"; +import { hideBin } from "yargs/helpers"; + +import { NativeMessagingVersion } from "@bitwarden/common/enums/nativeMessagingVersion"; + +import { CredentialUpdatePayload } from "../../../src/models/nativeMessaging/encryptedMessagePayloads/credentialUpdatePayload"; +import { LogUtils } from "../logUtils"; +import NativeMessageService from "../nativeMessageService"; +import * as config from "../variables"; + +// Command line arguments +const argv: any = yargs(hideBin(process.argv)) + .option("name", { + alias: "n", + demand: true, + describe: "Name that the updated login will be given", + type: "string", + }) + .option("username", { + alias: "u", + demand: true, + describe: "Username that the login will be given", + type: "string", + }) + .option("password", { + alias: "p", + demand: true, + describe: "Password that the login will be given", + type: "string", + }) + .option("uri", { + demand: true, + describe: "Uri that the login will be given", + type: "string", + }) + .option("credentialId", { + demand: true, + describe: "GUID of the credential to update", + type: "string", + }).argv; + +const { name, username, password, uri, credentialId } = argv; + +(async () => { + const nativeMessageService = new NativeMessageService(NativeMessagingVersion.One); + // Handshake + LogUtils.logInfo("Sending Handshake"); + const handshakeResponse = await nativeMessageService.sendHandshake( + config.testRsaPublicKey, + config.applicationName + ); + + if (!handshakeResponse.status) { + LogUtils.logError(" Handshake failed. Error was: " + handshakeResponse.error); + nativeMessageService.disconnect(); + return; + } + LogUtils.logSuccess("Handshake success response"); + + // Get active account userId + const status = await nativeMessageService.checkStatus(handshakeResponse.sharedKey); + + const activeUser = status.payload.filter((a) => a.active === true && a.status === "unlocked")[0]; + if (activeUser === undefined) { + LogUtils.logError("No active or unlocked user"); + } + LogUtils.logInfo("Active userId: " + activeUser.id); + + const response = await nativeMessageService.credentialUpdate(handshakeResponse.sharedKey, { + name: name, + password: password, + userName: username, + uri: uri, + userId: activeUser.id, + credentialId: credentialId, + } as CredentialUpdatePayload); + + if (response.payload.status === "failure") { + LogUtils.logError("Failure response returned "); + } else if (response.payload.status === "success") { + LogUtils.logSuccess("Success response returned "); + } else { + LogUtils.logWarning("Other response: ", response); + } + + nativeMessageService.disconnect(); +})(); diff --git a/apps/desktop/native-messaging-test-runner/src/commands/bw-generate-password.ts b/apps/desktop/native-messaging-test-runner/src/commands/bw-generate-password.ts new file mode 100644 index 00000000000..34bb41abb95 --- /dev/null +++ b/apps/desktop/native-messaging-test-runner/src/commands/bw-generate-password.ts @@ -0,0 +1,46 @@ +import "module-alias/register"; + +import yargs from "yargs"; +import { hideBin } from "yargs/helpers"; + +import { NativeMessagingVersion } from "@bitwarden/common/enums/nativeMessagingVersion"; + +import { LogUtils } from "../logUtils"; +import NativeMessageService from "../nativeMessageService"; +import * as config from "../variables"; + +const argv: any = yargs(hideBin(process.argv)).option("userId", { + alias: "u", + demand: true, + describe: "UserId to generate password for", + type: "string", +}).argv; + +const { userId } = argv; + +(async () => { + const nativeMessageService = new NativeMessageService(NativeMessagingVersion.One); + // Handshake + LogUtils.logInfo("Sending Handshake"); + const handshakeResponse = await nativeMessageService.sendHandshake( + config.testRsaPublicKey, + config.applicationName + ); + + if (!handshakeResponse.status) { + LogUtils.logError(" Handshake failed. Error was: " + handshakeResponse.error); + nativeMessageService.disconnect(); + return; + } + + LogUtils.logSuccess("Handshake success response"); + const response = await nativeMessageService.generatePassword(handshakeResponse.sharedKey, userId); + + if (response.payload.error != null) { + LogUtils.logError("Error response returned: ", response.payload.error); + } else { + LogUtils.logSuccess("Response: ", response); + } + + nativeMessageService.disconnect(); +})(); diff --git a/apps/desktop/native-messaging-test-runner/src/commands/bw-handshake.ts b/apps/desktop/native-messaging-test-runner/src/commands/bw-handshake.ts new file mode 100644 index 00000000000..f3098062c46 --- /dev/null +++ b/apps/desktop/native-messaging-test-runner/src/commands/bw-handshake.ts @@ -0,0 +1,25 @@ +import "module-alias/register"; + +import { NativeMessagingVersion } from "@bitwarden/common/enums/nativeMessagingVersion"; + +import { LogUtils } from "../logUtils"; +import NativeMessageService from "../nativeMessageService"; +import * as config from "../variables"; + +(async () => { + const nativeMessageService = new NativeMessageService(NativeMessagingVersion.One); + + const response = await nativeMessageService.sendHandshake( + config.testRsaPublicKey, + config.applicationName + ); + LogUtils.logSuccess("Received response to handshake request"); + if (response.status) { + LogUtils.logSuccess("Handshake success response"); + } else if (response.error === "canceled") { + LogUtils.logWarning("Handshake canceled by user"); + } else { + LogUtils.logError("Handshake failure response"); + } + nativeMessageService.disconnect(); +})(); diff --git a/apps/desktop/native-messaging-test-runner/src/commands/bw-status.ts b/apps/desktop/native-messaging-test-runner/src/commands/bw-status.ts new file mode 100644 index 00000000000..7782e203cb8 --- /dev/null +++ b/apps/desktop/native-messaging-test-runner/src/commands/bw-status.ts @@ -0,0 +1,29 @@ +import "module-alias/register"; + +import { NativeMessagingVersion } from "@bitwarden/common/enums/nativeMessagingVersion"; + +import { LogUtils } from "../logUtils"; +import NativeMessageService from "../nativeMessageService"; +import * as config from "../variables"; + +(async () => { + const nativeMessageService = new NativeMessageService(NativeMessagingVersion.One); + + LogUtils.logInfo("Sending Handshake"); + const handshakeResponse = await nativeMessageService.sendHandshake( + config.testRsaPublicKey, + config.applicationName + ); + LogUtils.logSuccess("Received response to handshake request"); + + if (!handshakeResponse.status) { + LogUtils.logError(" Handshake failed. Error was: " + handshakeResponse.error); + nativeMessageService.disconnect(); + return; + } + LogUtils.logSuccess("Handshake success response"); + const status = await nativeMessageService.checkStatus(handshakeResponse.sharedKey); + + LogUtils.logSuccess("Status output is: ", status); + nativeMessageService.disconnect(); +})(); diff --git a/apps/desktop/native-messaging-test-runner/src/deferred.ts b/apps/desktop/native-messaging-test-runner/src/deferred.ts new file mode 100644 index 00000000000..b6478bcf268 --- /dev/null +++ b/apps/desktop/native-messaging-test-runner/src/deferred.ts @@ -0,0 +1,26 @@ +// Wrapper for a promise that we can await the promise in one case +// while allowing an unrelated event to fulfill it elsewhere. +export default class Deferred { + private promise: Promise; + private resolver: (T?) => void; + private rejecter: (Error?) => void; + + constructor() { + this.promise = new Promise((resolve, reject) => { + this.resolver = resolve; + this.rejecter = reject; + }); + } + + resolve(value?: T) { + this.resolver(value); + } + + reject(error?: Error) { + this.rejecter(error); + } + + getPromise(): Promise { + return this.promise; + } +} diff --git a/apps/desktop/native-messaging-test-runner/src/ipcService.ts b/apps/desktop/native-messaging-test-runner/src/ipcService.ts new file mode 100644 index 00000000000..eadc69a3513 --- /dev/null +++ b/apps/desktop/native-messaging-test-runner/src/ipcService.ts @@ -0,0 +1,166 @@ +import { homedir } from "os"; + +import * as NodeIPC from "node-ipc"; + +import { MessageCommon } from "../../src/models/nativeMessaging/messageCommon"; +import { UnencryptedMessageResponse } from "../../src/models/nativeMessaging/unencryptedMessageResponse"; + +import Deferred from "./deferred"; +import { race } from "./race"; + +NodeIPC.config.id = "native-messaging-test-runner"; +NodeIPC.config.maxRetries = 0; +NodeIPC.config.silent = true; + +const DESKTOP_APP_PATH = `${homedir}/tmp/app.bitwarden`; +const DEFAULT_MESSAGE_TIMEOUT = 10 * 1000; // 10 seconds + +export type MessageHandler = (MessageCommon) => void; + +export enum IPCConnectionState { + Disconnected = "disconnected", + Connecting = "connecting", + Connected = "connected", +} + +export type IPCOptions = { + overrideTimeout?: number; +}; + +export default class IPCService { + // The current connection state of the socket. + private connectionState: IPCConnectionState = IPCConnectionState.Disconnected; + + // Messages that have been sent, but have not yet received responses + private pendingMessages = new Map>(); + + // A set of deferred promises that are awaiting socket connection + private awaitingConnection = new Set>(); + + constructor(private socketName: string, private messageHandler: MessageHandler) {} + + async connect(): Promise { + console.log("[IPCService] connecting..."); + if (this.connectionState === IPCConnectionState.Connected) { + // Socket is already connected. Don't throw, just allow the callsite to proceed + return; + } + + const deferredConnections = new Deferred(); + + this.awaitingConnection.add(deferredConnections); + + // If the current connection state is disconnected, we should start trying to connect. + // The only other possible connection state at this point is "connecting" and if this + // is the case, we just want to add a deferred promise to the awaitingConnection collection + // and not try to initiate the connection again. + if (this.connectionState === IPCConnectionState.Disconnected) { + this._connect(); + } + + return deferredConnections.getPromise(); + } + + private _connect() { + this.connectionState = IPCConnectionState.Connecting; + + NodeIPC.connectTo(this.socketName, DESKTOP_APP_PATH, () => { + // Process incoming message + this.getSocket().on("message", (message: any) => { + this.processMessage(message); + }); + + this.getSocket().on("error", (error: Error) => { + // Only makes sense as long as config.maxRetries stays set to 0. Otherwise this will be + // invoked multiple times each time a connection error happens + console.log("[IPCService] errored"); + console.log( + "\x1b[33m Please make sure the desktop app is running locally and 'Allow DuckDuckGo browser integration' setting is enabled \x1b[0m" + ); + this.awaitingConnection.forEach((deferred) => { + console.log(`rejecting: ${deferred}`); + deferred.reject(error); + }); + this.awaitingConnection.clear(); + }); + + this.getSocket().on("connect", () => { + console.log("[IPCService] connected"); + this.connectionState = IPCConnectionState.Connected; + + this.awaitingConnection.forEach((deferred) => { + deferred.resolve(null); + }); + this.awaitingConnection.clear(); + }); + + this.getSocket().on("disconnect", () => { + console.log("[IPCService] disconnected"); + this.connectionState = IPCConnectionState.Disconnected; + }); + }); + } + + disconnect() { + console.log("[IPCService] disconnecting..."); + if (this.connectionState !== IPCConnectionState.Disconnected) { + NodeIPC.disconnect(this.socketName); + } + } + + async sendMessage( + message: MessageCommon, + options: IPCOptions = {} + ): Promise { + console.log("[IPCService] sendMessage"); + if (this.pendingMessages.has(message.messageId)) { + throw new Error(`A message with the id: ${message.messageId} has already been sent.`); + } + + // Creates a new deferred promise that allows us to convert a message received over the IPC socket + // into a response for a message that we previously sent. This mechanism relies on the fact that we + // create a unique message id and attach it with each message. Response messages are expected to + // include the message id of the message they are responding to. + const deferred = new Deferred(); + + this.pendingMessages.set(message.messageId, deferred); + + this.getSocket().emit("message", message); + + try { + // Since we can not guarentee that a response message will ever be sent, we put a timeout + // on messages + return race({ + promise: deferred.getPromise(), + timeout: options?.overrideTimeout ?? DEFAULT_MESSAGE_TIMEOUT, + error: new Error(`Message: ${message.messageId} timed out`), + }); + } catch (error) { + // If there is a timeout, remove the message from the pending messages set + // before triggering error handling elsewhere. + this.pendingMessages.delete(message.messageId); + throw error; + } + } + + private getSocket() { + return NodeIPC.of[this.socketName]; + } + + private processMessage(message: any) { + // If the message is a response to a previous message, resolve the deferred promise that + // is awaiting that response. Otherwise, assume this was a new message that wasn't sent as a + // response and invoke the message handler. + if (message.messageId && this.pendingMessages.has(message.messageId)) { + const deferred = this.pendingMessages.get(message.messageId); + + // In the future, this could be improved to add ability to reject, but most messages coming in are + // encrypted at this point so we're unable to determine if they contain error info. + deferred.resolve(message); + + this.pendingMessages.delete(message.messageId); + } else { + this.messageHandler(message); + } + } +} diff --git a/apps/desktop/native-messaging-test-runner/src/logUtils.ts b/apps/desktop/native-messaging-test-runner/src/logUtils.ts new file mode 100644 index 00000000000..0e7c39742e3 --- /dev/null +++ b/apps/desktop/native-messaging-test-runner/src/logUtils.ts @@ -0,0 +1,29 @@ +/* eslint-disable no-console */ + +// Class for logging messages with colors for ease of reading important info +// Reference: https://stackoverflow.com/a/41407246 +export class LogUtils { + static logSuccess(message: string, payload?: any): void { + this.logFormat(message, "32", payload); + } + + static logWarning(message: string, payload?: any): void { + this.logFormat(message, "33", payload); + } + + static logError(message: string, payload?: any): void { + this.logFormat(message, "31", payload); + } + + static logInfo(message: string, payload?: any): void { + this.logFormat(message, "36", payload); + } + + private static logFormat(message: string, color: string, payload?: any) { + if (payload) { + console.log(`\x1b[${color}m ${message} \x1b[0m`, payload); + } else { + console.log(`\x1b[${color}m ${message} \x1b[0m`); + } + } +} diff --git a/apps/desktop/native-messaging-test-runner/src/nativeMessageService.ts b/apps/desktop/native-messaging-test-runner/src/nativeMessageService.ts new file mode 100644 index 00000000000..ada09064e3c --- /dev/null +++ b/apps/desktop/native-messaging-test-runner/src/nativeMessageService.ts @@ -0,0 +1,235 @@ +import "module-alias/register"; + +import { v4 as uuidv4 } from "uuid"; + +import { Utils } from "@bitwarden/common/misc/utils"; +import { EncString } from "@bitwarden/common/models/domain/encString"; +import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCryptoKey"; +import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service"; +import { EncryptService } from "@bitwarden/common/services/encrypt.service"; +import { NodeCryptoFunctionService } from "@bitwarden/node/services/nodeCryptoFunction.service"; + +import { DecryptedCommandData } from "../../src/models/nativeMessaging/decryptedCommandData"; +import { EncryptedMessage } from "../../src/models/nativeMessaging/encryptedMessage"; +import { CredentialCreatePayload } from "../../src/models/nativeMessaging/encryptedMessagePayloads/credentialCreatePayload"; +import { CredentialUpdatePayload } from "../../src/models/nativeMessaging/encryptedMessagePayloads/credentialUpdatePayload"; +import { EncryptedMessageResponse } from "../../src/models/nativeMessaging/encryptedMessageResponse"; +import { MessageCommon } from "../../src/models/nativeMessaging/messageCommon"; +import { UnencryptedMessage } from "../../src/models/nativeMessaging/unencryptedMessage"; +import { UnencryptedMessageResponse } from "../../src/models/nativeMessaging/unencryptedMessageResponse"; + +import IPCService, { IPCOptions } from "./ipcService"; +import * as config from "./variables"; + +type HandshakeResponse = { + status: boolean; + sharedKey: string; + error?: "canceled" | "cannot-decrypt"; +}; + +const CONFIRMATION_MESSAGE_TIMEOUT = 100 * 1000; // 100 seconds + +export default class NativeMessageService { + private ipcService: IPCService; + private nodeCryptoFunctionService: NodeCryptoFunctionService; + private encryptService: EncryptService; + + constructor(private apiVersion: number) { + console.log("Starting native messaging service"); + this.ipcService = new IPCService(`bitwarden`, (rawMessage) => { + console.log(`Received unexpected: `, rawMessage); + }); + + this.nodeCryptoFunctionService = new NodeCryptoFunctionService(); + this.encryptService = new EncryptService( + this.nodeCryptoFunctionService, + new ConsoleLogService(false), + false + ); + } + + // Commands + + async sendHandshake(publicKey: string, applicationName: string): Promise { + const rawResponse = await this.sendUnencryptedMessage( + { + command: "bw-handshake", + payload: { + publicKey, + applicationName: applicationName, + }, + }, + { + overrideTimeout: CONFIRMATION_MESSAGE_TIMEOUT, + } + ); + return rawResponse.payload as HandshakeResponse; + } + + async checkStatus(key: string): Promise { + const encryptedCommand = await this.encryptCommandData( + { + command: "bw-status", + }, + key + ); + + const response = await this.sendEncryptedMessage({ + encryptedCommand, + }); + + return this.decryptResponsePayload(response.encryptedPayload, key); + } + + async credentialRetrieval(key: string, uri: string): Promise { + const encryptedCommand = await this.encryptCommandData( + { + command: "bw-credential-retrieval", + payload: { + uri: uri, + }, + }, + key + ); + const response = await this.sendEncryptedMessage({ + encryptedCommand, + }); + + return this.decryptResponsePayload(response.encryptedPayload, key); + } + + async credentialCreation( + key: string, + credentialData: CredentialCreatePayload + ): Promise { + const encryptedCommand = await this.encryptCommandData( + { + command: "bw-credential-create", + payload: credentialData, + }, + key + ); + const response = await this.sendEncryptedMessage({ + encryptedCommand, + }); + + return this.decryptResponsePayload(response.encryptedPayload, key); + } + + async credentialUpdate( + key: string, + credentialData: CredentialUpdatePayload + ): Promise { + const encryptedCommand = await this.encryptCommandData( + { + command: "bw-credential-update", + payload: credentialData, + }, + key + ); + const response = await this.sendEncryptedMessage({ + encryptedCommand, + }); + + return this.decryptResponsePayload(response.encryptedPayload, key); + } + + async generatePassword(key: string, userId: string): Promise { + const encryptedCommand = await this.encryptCommandData( + { + command: "bw-generate-password", + payload: { + userId: userId, + }, + }, + key + ); + const response = await this.sendEncryptedMessage({ + encryptedCommand, + }); + + return this.decryptResponsePayload(response.encryptedPayload, key); + } + + // Private message sending + + private async sendEncryptedMessage( + message: Omit, + options: IPCOptions = {} + ): Promise { + const result = await this.sendMessage(message, options); + return result as EncryptedMessageResponse; + } + + private async sendUnencryptedMessage( + message: Omit, + options: IPCOptions = {} + ): Promise { + const result = await this.sendMessage(message, options); + return result as UnencryptedMessageResponse; + } + + private async sendMessage( + message: + | Omit + | Omit, + options: IPCOptions + ): Promise { + // Attempt to connect before sending any messages. If the connection has already + // been made, this is a NOOP within the IPCService. + await this.ipcService.connect(); + + const commonFields: MessageCommon = { + // Create a messageId that can be used as a lookup when we get a response + messageId: uuidv4(), + version: this.apiVersion, + }; + const fullMessage: UnencryptedMessage | EncryptedMessage = { + ...message, + ...commonFields, + }; + + console.log(`[NativeMessageService] sendMessage with id: ${fullMessage.messageId}`); + + const response = await this.ipcService.sendMessage(fullMessage, options); + + console.log(`[NativeMessageService] received response for message: ${fullMessage.messageId}`); + + return response; + } + + disconnect() { + this.ipcService.disconnect(); + } + + // Data Encryption + private async encryptCommandData( + commandData: DecryptedCommandData, + key: string + ): Promise { + const commandDataString = JSON.stringify(commandData); + + const sharedKey = await this.getSharedKeyForKey(key); + + return this.encryptService.encrypt(commandDataString, sharedKey); + } + + private async decryptResponsePayload( + payload: EncString, + key: string + ): Promise { + const sharedKey = await this.getSharedKeyForKey(key); + const decrypted = await this.encryptService.decryptToUtf8(payload, sharedKey); + + return JSON.parse(decrypted); + } + + private async getSharedKeyForKey(key: string): Promise { + const dataBuffer = Utils.fromB64ToArray(key).buffer; + const privKey = Utils.fromB64ToArray(config.testRsaPrivateKey).buffer; + + return new SymmetricCryptoKey( + await this.nodeCryptoFunctionService.rsaDecrypt(dataBuffer, privKey, "sha1") + ); + } +} diff --git a/apps/desktop/native-messaging-test-runner/src/race.ts b/apps/desktop/native-messaging-test-runner/src/race.ts new file mode 100644 index 00000000000..7aba3aa41f9 --- /dev/null +++ b/apps/desktop/native-messaging-test-runner/src/race.ts @@ -0,0 +1,25 @@ +export const race = ({ + promise, + timeout, + error, +}: { + promise: Promise; + timeout: number; + error?: Error; +}) => { + let timer = null; + + // Similar to Promise.all, but instead of waiting for all, it resolves once one promise finishes. + // Using this so we can reject if the timeout threshold is hit + return Promise.race([ + new Promise((_, reject) => { + timer = setTimeout(reject, timeout, error); + return timer; + }), + + promise.then((value) => { + clearTimeout(timer); + return value; + }), + ]); +}; diff --git a/apps/desktop/native-messaging-test-runner/src/variables.ts b/apps/desktop/native-messaging-test-runner/src/variables.ts new file mode 100644 index 00000000000..973da2c224b --- /dev/null +++ b/apps/desktop/native-messaging-test-runner/src/variables.ts @@ -0,0 +1,27 @@ +export const applicationName = "Native Messaging Test Runner"; +export const encryptionAlogrithm = "sha1"; +export const testRsaPublicKey = + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl0Vawl/toXzkEvB82FEtqHP" + + "4xlU2ab/v0crqIfXfIoWF/XXdHGIdrZeilnRXPPJT1B9dTsasttEZNnua/0Rek/cjNDHtzT52irfoZYS7X6HNIfOi54Q+egP" + + "RQ1H7iNHVZz3K8Db9GCSKPeC8MbW6gVCzb15esCe1gGzg6wkMuWYDFYPoh/oBqcIqrGah7firqB1nDedzEjw32heP2DAffVN" + + "084iTDjiWrJNUxBJ2pDD5Z9dT3MzQ2s09ew1yMWK2z37rT3YerC7OgEDmo3WYo3xL3qYJznu3EO2nmrYjiRa40wKSjxsTlUc" + + "xDF+F0uMW8oR9EMUHgepdepfAtLsSAQIDAQAB"; +export const testRsaPrivateKey = + "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXRVrCX+2hfOQS8Hz" + + "YUS2oc/jGVTZpv+/Ryuoh9d8ihYX9dd0cYh2tl6KWdFc88lPUH11Oxqy20Rk2e5r/RF6T9yM0Me3NPnaKt+hlhLtfoc0h86L" + + "nhD56A9FDUfuI0dVnPcrwNv0YJIo94LwxtbqBULNvXl6wJ7WAbODrCQy5ZgMVg+iH+gGpwiqsZqHt+KuoHWcN53MSPDfaF4/" + + "YMB99U3TziJMOOJask1TEEnakMPln11PczNDazT17DXIxYrbPfutPdh6sLs6AQOajdZijfEvepgnOe7cQ7aeatiOJFrjTApK" + + "PGxOVRzEMX4XS4xbyhH0QxQeB6l16l8C0uxIBAgMBAAECggEASaWfeVDA3cVzOPFSpvJm20OTE+R6uGOU+7vh36TX/POq92q" + + "Buwbd0h0oMD32FxsXywd2IxtBDUSiFM9699qufTVuM0Q3tZw6lHDTOVG08+tPdr8qSbMtw7PGFxN79fHLBxejjO4IrM9lapj" + + "WpxEF+11x7r+wM+0xRZQ8sNFYG46aPfIaty4BGbL0I2DQ2y8I57iBCAy69eht59NLMm27fRWGJIWCuBIjlpfzET1j2HLXUIh" + + "5bTBNzqaN039WH49HczGE3mQKVEJZc/efk3HaVd0a1Sjzyn0QY+N1jtZN3jTRbuDWA1AknkX1LX/0tUhuS3/7C3ejHxjw4Dk" + + "1ZLo5/QKBgQDIWvqFn0+IKRSu6Ua2hDsufIHHUNLelbfLUMmFthxabcUn4zlvIscJO00Tq/ezopSRRvbGiqnxjv/mYxucvOU" + + "BeZtlus0Q9RTACBtw9TGoNTmQbEunJ2FOSlqbQxkBBAjgGEppRPt30iGj/VjAhCATq2MYOa/X4dVR51BqQAFIEwKBgQDBSIf" + + "TFKC/hDk6FKZlgwvupWYJyU9RkyfstPErZFmzoKhPkQ3YORo2oeAYmVUbS9I2iIYpYpYQJHX8jMuCbCz4ONxTCuSIXYQYUcU" + + "q4PglCKp31xBAE6TN8SvhfME9/MvuDssnQinAHuF0GDAhF646T3LLS1not6Vszv7brwSoGwKBgQC88v/8cGfi80ssQZeMnVv" + + "q1UTXIeQcQnoY5lGHJl3K8mbS3TnXE6c9j417Fdz+rj8KWzBzwWXQB5pSPflWcdZO886Xu/mVGmy9RWgLuVFhXwCwsVEPjNX" + + "5ramRb0/vY0yzenUCninBsIxFSbIfrPtLUYCc4hpxr+sr2Mg/y6jpvQKBgBezMRRs3xkcuXepuI2R+BCXL1/b02IJTUf1F+1" + + "eLLGd7YV0H+J3fgNc7gGWK51hOrF9JBZHBGeOUPlaukmPwiPdtQZpu4QNE3l37VlIpKTF30E6mb+BqR+nht3rUjarnMXgAoE" + + "Z18y6/KIjpSMpqC92Nnk/EBM9EYe6Cf4eA9ApAoGAeqEUg46UTlJySkBKURGpIs3v1kkf5I0X8DnOhwb+HPxNaiEdmO7ckm8" + + "+tPVgppLcG0+tMdLjigFQiDUQk2y3WjyxP5ZvXu7U96jaJRI8PFMoE06WeVYcdIzrID2HvqH+w0UQJFrLJ/0Mn4stFAEzXKZ" + + "BokBGnjFnTnKcs7nv/O8="; diff --git a/apps/desktop/native-messaging-test-runner/tsconfig.json b/apps/desktop/native-messaging-test-runner/tsconfig.json new file mode 100644 index 00000000000..a34554a264f --- /dev/null +++ b/apps/desktop/native-messaging-test-runner/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "baseUrl": "./", + "outDir": "dist", + "target": "es6", + "module": "CommonJS", + "moduleResolution": "node", + "sourceMap": false, + "declaration": false, + "paths": { + "@src/*": ["src/*"], + "@bitwarden/node/*": ["../../../libs/node/src/*"], + "@bitwarden/common/*": ["../../../libs/common/src/*"] + } + }, + "exclude": ["node_modules"] +} diff --git a/apps/desktop/src/app/accounts/settings.component.html b/apps/desktop/src/app/accounts/settings.component.html index 420ee20a39e..a1ecc0732f9 100644 --- a/apps/desktop/src/app/accounts/settings.component.html +++ b/apps/desktop/src/app/accounts/settings.component.html @@ -309,6 +309,23 @@ {{ "enableBrowserIntegrationDesc" | i18n }} +
+
+ +
+ {{ + "enableDuckDuckGoBrowserIntegrationDesc" | i18n + }} +